Last active
December 11, 2015 22:08
-
-
Save bf4/4667296 to your computer and use it in GitHub Desktop.
Multiple table inheritance in Rails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Multiple table inheritance in Rails | |
# from http://web.archive.org/web/20120406010350/http://mediumexposure.com/multiple-table-inheritance-active-record | |
# the below code creates a macro 'acts_as_title' that you can include in an AR model | |
# including 'acts_as_title' adds the attributes from the TitleProperties model/table | |
# so that, e.g. a TvTitle can have attributes that are a combination | |
# of the tv_titles table and the title_properties table | |
# and behaves more or less as a normal AR object. | |
# | |
# yeah this looks a little nuts and needs refactoring | |
#config/initializers/acts_as_title.rb | |
class ActiveRecord::Base | |
def self.acts_as_title | |
include ActsAsTitle | |
end | |
end | |
#app/models/title_properties | |
class TitleProperties < ActiveRecord::Base | |
self.table_name = 'titles' | |
belongs_to :acts_as_title, :polymorphic => true, :dependent => :destroy | |
end | |
class AddTitleTypeToTitleTable < ActiveRecord::Migration | |
def change | |
add_column :titles, :title_type, :string, :null => false | |
add_column :titles, :title_id, :integer, :null => false | |
add_index :titles, [:title_id, :title_type] | |
end | |
end | |
#app/models/tv_title.rb | |
class TvTitle < ActiveRecord::Base | |
acts_as_title | |
end | |
#app/models/acts_as_title.rb | |
module ActsAsTitle | |
def self.included(base) | |
base.has_one :title_properties, :as => :title, :autosave => true, :dependent => :destroy | |
base.validate :title_properties_must_be_valid | |
base.alias_method_chain :title_properties, :autobuild | |
base.extend ClassMethods | |
base.define_title_properties_accessors | |
base.define_nested_attributes_accessors | |
base.define_title_properties_associations_accessors | |
base.mix_in_title_properties_attributes | |
end | |
def title_properties_with_autobuild | |
title_properties_without_autobuild || build_title_properties | |
end | |
def method_missing(meth, *args, &blk) | |
title_properties.send(meth, *args, &blk) | |
rescue NoMethodError | |
super | |
end | |
module ClassMethods | |
def define_title_properties_accessors | |
all_attributes = TitleProperties.content_columns.map(&:name) | |
ignored_attributes = ["created_at", "updated_at", "title_type"] | |
attributes_to_delegate = all_attributes - ignored_attributes | |
attributes_to_delegate.each do |attrib| | |
class_eval <<-RUBY | |
def #{attrib} | |
title_properties.#{attrib} | |
end | |
def #{attrib}=(value) | |
self.title_properties.#{attrib} = value | |
end | |
def #{attrib}? | |
self.title_properties.#{attrib}? | |
end | |
RUBY | |
end | |
end | |
def define_nested_attributes_accessors | |
TitleProperties.nested_attributes_options.keys.each do |attrib| | |
module_eval <<-RUBY | |
delegate :#{attrib}_attributes=, :to => :title_properties, :allow_nil => true | |
RUBY | |
end | |
end | |
def define_title_properties_associations_accessors | |
associations = TitleProperties.reflect_on_all_associations | |
associations.map(&:name).each do |attrib| | |
class_eval <<-RUBY | |
def #{attrib} | |
title_properties.#{attrib} | |
end | |
def #{attrib}=(value) | |
self.title_properties.#{attrib} = value | |
end | |
def #{attrib}? | |
self.title_properties.#{attrib}? | |
end | |
RUBY | |
end | |
TitleProperties.generated_feature_methods.public_instance_methods(false).grep(/_ids?\Z/).each do |attrib| | |
class_eval <<-RUBY | |
def #{attrib} | |
self.title_properties.#{attrib} | |
end | |
def #{attrib}=(value) | |
self.title_properties.#{attrib} = value | |
end | |
RUBY | |
end | |
instance_eval <<-RUBY | |
def reflect_on_all_associations | |
associations = TitleProperties.reflect_on_all_associations | |
super + associations | |
end | |
def reflect_on_association(assoc) | |
TitleProperties.reflect_on_association(assoc) || super | |
end | |
RUBY | |
end | |
def mix_in_title_properties_attributes | |
class_eval <<-RUBY | |
def column_for_attribute(attrib) | |
super || title_properties.column_for_attribute(attrib) | |
end | |
def attributes | |
super.merge(:title_properties => title_properties.attributes) | |
end | |
RUBY | |
end | |
end | |
protected | |
def title_properties_must_be_valid | |
unless title_properties.valid? | |
title_properties.errors.each do |attr, message| | |
errors.add(attr, message) | |
end | |
end | |
end | |
end | |
#app/inputs/fake_input.rb | |
# from https://github.com/plataformatec/simple_form/wiki/Create-an-%22input%22-just-for-displaying-attribute-value | |
# useful in adding values to a simple_form that it doesn't understand, but I do :) | |
class FakeInput < SimpleForm::Inputs::StringInput | |
# This method only create a basic input without reading any value from object | |
def input | |
template.text_field_tag(attribute_name, nil, input_html_options) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment