Module: RussellEdge::HasManyPolymorphic::ClassMethods

Defined in:
lib/has_many_polymorphic/has_many_polymorphic.rb

Instance Method Summary collapse

Instance Method Details

#has_many_polymorphic(name, options = {}) ⇒ Object

HasManyPolymorphic

This mixin adds a has many polymorphic relationship to a model and creates all the relationships needed
by rails to handle it.

Params

Name - name of the relationship, there is a convention that whatever name you choose, the polymorphic
table columns on your through table should match.

Options
 - through - the model that handles the through relationship
 - models  - models that should be included in this polymophic relationship

One method is added for you to use

- {name param}
  - the name of your relationship is used for the method name of this method. it will return
  an array of the models that are related via the has_many relationships

There is an after_save call back that will save the relationships when they are added or removed
If you want to remove a relationship the models need to be destroyed and this model reloaded.

Example Usage

class PreferenceType < ActiveRecord::Base
 has_many_polymorphic :preferenced_records,
     :through => :valid_preference_types,
     :models => [:desktops, :organizers]
end

this gives you the following

preferenceType = PreferenceType.first
preferenceType.desktops
preferenceType.organizers

and

preferenceType.preferenced_records

which is a concatentated array of the models. You also get the following

desktop = Desktop.first
desktop.preference_types

organizer = Organizer.first
organizer.preference_types


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/has_many_polymorphic/has_many_polymorphic.rb', line 61

def has_many_polymorphic(name, options = {})
  target_class_name = self.name
  instance_array_name = "#{name}_array".to_sym

  #declare array to related models
  attr_accessor instance_array_name

  #create the has_many relationship
  has_many options[:through], :dependent => :destroy

  #create the has_many relationship for each model
  options[:models].each do |model|
    has_many model.to_sym, -> { where("#{options[:through]}.#{name.to_s.singularize}_type = ?", model.to_s.classify) },
      :through => options[:through],
      :source => model.to_s.singularize,
      :dependent => :destroy
  end

  #modify the through class to add the belongs to relationships
  options[:through].to_s.classify.constantize.class_exec do
    options[:models].each do |model|
      belongs_to model.to_s.singularize.to_sym, :class_name => model.to_s.classify, :foreign_key => "#{name.to_s.singularize}_id"
    end
  end

  #I want to keep the << and push methods of array so this helps to keep them.
  define_method name do
    #used the declared instance variable array
    records = self.send(instance_array_name.to_s)
    records = records || []
    options[:models].each do |model|
      records = records | self.send(model.to_s)
    end

    #set it back to the instance variable
    self.send("#{instance_array_name.to_s}=", records)

    records
  end

  #before we save this model make sure you save all the relationships.
  before_save do |record|
    record.send(name).each do |reln_record|
      #handle STI get superclass class_name if not sub class of ActiveRecord::Base
      klass_name = (reln_record.class.superclass == ::ApplicationRecord) ? reln_record.class.name : reln_record.class.superclass.name
      conditions = "#{name.to_s.singularize}_id = #{reln_record.id} and #{name.to_s.singularize}_type = '#{klass_name}'"
      exisiting_record = record.send("#{options[:through]}").where(conditions).first

      if exisiting_record.nil?
        values_hash = {}
        values_hash["#{record.class.name.underscore}_id"] = record.id
        values_hash["#{name.to_s.singularize}_type"] = klass_name
        values_hash["#{name.to_s.singularize}_id"] = reln_record.id

        options[:through].to_s.classify.constantize.create(values_hash)
      end
    end
  end

  #add the relationship to the models.
  options[:models].each do |model|
    model.to_s.classify.constantize.class_exec do
      has_many options[:through], :as => name.to_s.singularize, :dependent => :destroy
      has_many target_class_name.tableize.to_sym, :through => options[:through], :dependent => :destroy
    end
  end

end