GithubHelp home page GithubHelp logo

heritage's Introduction

Heritage

Heritage is a gem that implements Multiple Table Inheritance for ActiveRecord models.

Compatability

Heritage has only been tested with Rails 3

Installation

Simply add Heritage to your Gemfile and bundle it up:

  gem 'heritage'

Usage

Heritage works by assigning one model as your predecessor, and one or more other models as it’s heir.
The predecessor is the parent of it’s heirs, and thereby implicitly gives it’s heirs access to it’s columns, and optionally exposing methods to them.

To mark a model as predecessor, simply use the acts_as_predecessor class-method:

  class Post < ActiveRecord::Base
    acts_as_predecessor
  end

To mark a model as heir, simply use the acts_as_heir_of class-method, passing a symbol to the model that is to be the heirs predecessor.

  class BlogPost < ActiveRecord::Base
    acts_as_heir_of :post
  end

This takes care of the model configuration. We however need to add two extra columns to the Posts table.
We need a heir_id column of type integer and a heir_type column of type string.

  class CreatePosts < ActiveRecord::Migration
    def self.up
      create_table :posts do |t|
        t.integer :heir_id
        t.string :heir_type
        t.string :title
        t.timestamps
      end
    end

    def self.down
      drop_table :posts
    end
  end
  
  class CreateBlogPosts < ActiveRecord::Migration
    def self.up
      create_table :blog_posts do |t|
        t.text :body
      end
    end

    def self.down
      drop_table :blog_posts
    end
  end

When this is done and the database is migrated, we can begin using the models.

Creating new instances

Now we can simply call the following to create a new BlogPost

  blog_post = BlogPost.create(:title => "Wow", :body => "That's a nice blog post!")

Notice that the title attribute belongs to the Post model, and the body attribute belongs to the BlogPost model.

Attributes

We can directly access the title attribute through BlogPost and even change it’s value

  blog_post.title # "Wow"
  blog_post.title = "Oh boy!"
  blog_post.save!
  blog_post.title # "Oh boy!"

We can also update attributes like normal through update_attributes

  blog_post.update_attributes(:title => "Hubba Hubba", :body => "Nice blog post!")
  blog_post.title # "Hubba Hubba"
  blog_post.body # "Nice blog post!"

Methods

If we want to expose some methods from our predecessor model to it’s heirs, we can do so when calling the acts_as_predecessor class-method

  class Post < ActiveRecord::Base

    acts_as_predecessor :exposes => :hello

    def hello
      "Hi there!"
    end

  end

Now all heirs of Post will have a hello-method, which we can call directly on the heir-model:

  blog_post = BlogPost.create(:title => "I am full", :body => "of methods...")
  blog_post.hello # "Hi there!"

If you for some reason need to override the method in one of your heir-models, you can simply implement the method, and it will override the method from the predecessor.

  class BlogPost < ActiveRecord::Base

    acts_as_heir_of :post

    def hello
      "Yo!"
    end

  end

Calling the hello method on BlogPost will now yield another result:

  blog_post = BlogPost.create(:title => "I have", :body => "my own methods...")
  blog_post.hello # "Yo!"

If we need to combine the local method in the heir, with the method in the predecessor, we can do so through the predecessor method of the heir model, kinda like you would use super.

  class BlogPost < ActiveRecord::Base

    acts_as_heir_of :post

    def hello
      "Yo! #{predecessor.hello}"
    end

  end

The result would now be a combination of the local method in the heir, and the method in the predecessor:

  blog_post = BlogPost.create(:title => "I have", :body => "my own methods...")
  blog_post.hello # "Yo! Hi there!"

Listing and filtering

To list all your wonderful heir models you do as you normally would in ActiveRecord, with one single exception.

Normally you would call something like this, to show all BlogPosts

  @posts = BlogPost.all

This however will result in 1 + the number of returned records SQL calls, which is hardly good.
Instead you need to tell ActiveRecord that it should include the predecessors of the heirs, like so:

  @posts = BlogPost.all(:include => :predecessor)

We now only call the database twice; Once for loading the heirs, and once for loading all referenced predecessors.

Another gotcha is when you need to filter the heirs. You can’t directly filter by attributes from the predecessor model.
So in our example where we have the title attribute in the Post model, we can’t do the following:

  @posts = BLogPost.where("title = 'test'")

Instead we need to join the predecessor attributes by its association, like so:

  @posts = BlogPost.joins(:predecessor).where("posts.title = 'test'")

Behind the scenes, heritage works just like a simple ActiveRecord association, so it makes sense.

Timestamps

If all of your heir-models needs timestamps, then you can simply add timestamps to the predecessor model, and omit them from the heir-models.
Heritage will make sure, that whenever you update your heir-model, the updated_at timestamp in the predecessor model will be updated.

A note on destruction

Heritage depends on the destroy-method of the models, and as such you should always delete predecessor and heir models by calling the destroy method on either, and NEVER by calling the delete or delete_all methods.
If you absolutely need to do a direct delete in the database, then you need to manually remove the counterpart as well.

For instance, if you manually delete a BlogPost that is heir of Post, then you need to first find the right Post, then delete the heir and finally delete the predecessor.

Advanced usage

It is always possible to traverse between a predecessor and it’s associated heir, through the predecessor method of an heir, and the heir method of a predecessor.

Questions, Feedback

Feel free to message me on Github (murui)

Contributing to Heritage

Fork, fix, then send me a pull request.

Credits

Credits goes out to Gerry from TechSpry.com for the idea for this implementation:
http://techspry.com/ruby_and_rails/multiple-table-inheritance-in-rails-3/

License

Creative Commons License
Heritage by Thomas Dippel @ Benjamin Media A/S is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Based on a work at techspry.com

heritage's People

Contributors

davidgeere avatar dipth avatar rharriso avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

heritage's Issues

Error using acts_as_heir_of when use underscore '_' in a model filename

Hi,

When i try use your software to inherit my im_image table from im_file model, i received the following error

Expected /Users/hsobral/sistemas/imager/app/models/im_file.rb to define Im_file

I founded this two lines using capitalize. I think the right would be to use the camelize

self._predecessor_klass = Object.const_get(predecessor_symbol.to_s.capitalize)
...
has_one :predecessor, :as => :heir, :class_name => predecessor_symbol.to_s.camelize, :autosave => true, :dependent => :destroy

Thanks.
Henrique

Ordering by

Just trying to order by a predecessor column. Is there an easy way to do this? cause I keep getting the "no such column" error.

Thanks!

Question: Predecessor destruction on heir.destroy

Hi,
and thank for your gem. I've 2 classes : Media (predecessor) and Image(heir). I'm trying to destroy a media's instance (destroy) through the Image.destroy method.
Actually, the "predecessor" class is destroyed by "heir".destroy but the reciprocal isn't working.

Could you indicate me how could I fix this issue ?

Thx

problem accessing parent attributes

Just downloaded the gem and made some test on the console.
I get an error when I try to get values of attributes of the parent table (posts).
I launch the following command: @posts = BlogPost.where("posts.title = 'test'") and here is the error message I get:

ERROR: missing FROM-clause entry for table "posts"
LINE 1: SELECT "blog_posts".* FROM "blog_posts" WHERE (posts.title ...

Is that a known issue?

Stack Level too deep with 'Resque'

Hello,

First, thank you for this tool which is very easy to use.

I met a problem with 'Resque' and 'Heritage' -> https://github.com/defunkt/resque

I have a class SoapMonitor which inherites Monitor.

require 'snmp'

class SoapMonitor < ActiveRecord::Base

        attr_accessible :name, :description, :host, :port, :wsdl_filename

        acts_as_heir_of :monitor

        validates_presence_of :name, :host
end

A class with a perform method ( Job that worker have to execute)

require 'resque'
require 'soapmonitor'

class Update_SoapMonitor

@queue = :SoapMonitor_queue


        def self.perform(webio_id)
                ActiveRecord::Base.verify_active_connections!
                soapm = SoapMonitor.find(webio_id)
                puts soapm.wsdl_filename #This is OK (Attribute from SoapMonitor)
                puts soapm.host #This is not OK (Attribute from Monitor)
        end

end

soapm.host returns me :

Class
    Update_SoapMonitor
Arguments
    "1"

Exception
    SystemStackError
Error
    stack level too deep

    /usr/lib/ruby/gems/1.8/gems/heritage-0.3.1/lib/heritage/active_record/acts_as_heir.rb:57:in `predecessor_without_build'
    /usr/lib/ruby/gems/1.8/gems/heritage-0.3.1/lib/heritage/active_record/acts_as_heir.rb:58:in `predecessor'
    /usr/lib/ruby/gems/1.8/gems/heritage-0.3.1/lib/heritage/active_record/acts_as_heir.rb:20:in `host'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:165:in `send'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:165:in `method_missing'
    /var/www/html/MyApp/app/models/soapMonitor.rb:32:in `create_soap_client'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:159:in `instance_eval'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:159:in `evaluate'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:147:in `process'
    /usr/lib/ruby/gems/1.8/gems/savon-0.9.9/lib/savon/client.rb:34:in `initialize'
    /var/www/html/MyApp/app/models/soapMonitor.rb:31:in `new'
    /var/www/html/MyApp/app/models/soapMonitor.rb:31:in `create_soap_client'
    /var/www/html/MyApp/app/models/soapMonitor.rb:80:in `get_value'
    /var/www/html/MyApp/app/models/soapMonitor.rb:41:in `version'
    /var/www/html/MyApp/app/workers/Update_SoapMonitor.rb:28:in `perform'

Do you have any suggestions to help me ?

Bests Regards,

Question: heritage and ActiveRecord associations

Hi,

Thanks for the great gem.

I couldn't figure out how to implement associations when the type of the heir is unknown.
Example: I want to do something like:

class Post < ActiveRecord::Base
  acts_as_predecessor
  belongs_to :site
end

class BlogPost < ActiveRecord::Base
  acts_as_heir_of :post
end

class ImagePost < ActiveRecord::Base
  acts_as_heir_of :post
end

class Site < ActiveRecord::Base
  has_many :posts #could be BlogPost or ImagePost 

  def create_some_posts
      post1 = BlogPost.create
      posts << post1
      post2 = ImagePost.create
      posts << post2
  end
end

How should I fix this code? Please advise. Thanks!

Heirs not respecting predecessor validation?

I'm able to create child objects without satisfying the validation on the parent object.

When I try to create the parent object directly without the required field, I get the validation error like I'm supposed to. However, no complaints when I create the child object without the required parent field set.

Cyclic update

It's a perfomance issue.
It sends cyclic update request on model update,
I have Product model as a predecessor and Movie as a heir.
When I update movie it sends 4 request to DB instead of 2.
Here is a simple example to reproduce:

mov.name = 'a_new_name'
mov.save
(0.2ms)  UPDATE "products" SET "name" = 'one', "updated_at" = '2011-09-01 14:17:55.933125' WHERE "products"."id" = 2
SQL (0.1ms)  UPDATE "movies" SET "updated_at" = '2011-09-01 14:17:55.934588' WHERE "movies"."id" = 2
SQL (0.0ms)  UPDATE "products" SET "updated_at" = '2011-09-01 14:17:55.935150' WHERE "products"."id" = 2
SQL (0.0ms)  UPDATE "movies" SET "updated_at" = '2011-09-01 14:17:55.935528' WHERE "movies"."id" = 2

Environment:
DB: SQLIte3
Rails: 3.1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.