module DattsRight module InstanceMethods def add_dynamic_attribute(name, klass) unless attributes.keys.include?(name.to_s) name = name.to_s datt = datts.find_by_attr_key(name.underscore) unless datt datt = datts.create :name => name, :attr_key => name.underscore, :object_type => klass dynamic_columns(true) end #puts "Just added #{name} to #{self.class.name}##{id}, and here are the dynamic_attributes: #{dynamic_columns}" datt else false end end def remove_dynamic_attribute(name) name = name.to_s datt = datts.find_by_attr_key(name) if datt datt.destroy dynamic_columns(true) end end # Works like ActiveRecord's attributes except it returns dynamic attributes only def dynamic_attributes(reload=false) @dynamic_attributes ||= {} return @dynamic_attributes if !reload #puts "Reloading dynamic_attributes" datts.each do |datt| #puts "Adding this to @dynamic_attributes: #{datt.attr_key} => #{datt.value}" @dynamic_attributes.merge({datt.attr_key => datt.value}) end #puts "Here are teh datts: #{datts.inspect}, and this is what we're returning: #{@dynamic_attributes}" @dynamic_attributes.symbolize_keys! end # Because we cannot determine the difference between the inexistence # between key value pair point to nil (eg :hi => nil) and # the key not existing, we need to have a different method to return # information about the record in this format: # {:price => {:object_type => "integer", :value => 200}} def dynamic_columns(reload=false) @dynamic_columns ||= {} @dynamic_columns if !reload #puts "Building the dynamic_columns cache, and we'll look through #{datts.count} datts" @dynamic_columns={} datts.reload datts.each do |datt| #puts "Going through datt##{datt.id}" dynamic_column = { datt.attr_key.to_sym => { :object_type => datt.object_type, :value => datt.value } } #puts "Added #{dynamic_column} to the dynamic columns" @dynamic_columns.merge!(dynamic_column) end #puts "in dynamic_columns, returning: #{@dynamic_columns.inspect}" @dynamic_columns end # Determines if the given attribute is a dynamic attribute. def dynamic_attribute?(attr) #puts "in dynamic_attribute?(:#{attr}). datts: #{datts.inspect}. Datt: #{Datt.all.inspect}" #dynamic_columns.include?(attr.to_s) !dynamic_columns[attr.to_sym].nil? #return dynamic_attributes.include?(attr.to_sym) unless dynamic_attributes.empty? #return false if self.class.column_names.include?(attr.to_s) #false end # This overrides the attributes= defined in ActiveRecord::Base # The only difference is that this doesn't check to see if the # model responds_to the method before sending it #def attributes=(new_attributes, guard_protected_attributes = true) #return unless new_attributes.is_a?(Hash) #attributes = new_attributes.stringify_keys #multi_parameter_attributes = [] #attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes #attributes.each do |k, v| #if k.include?("(") #multi_parameter_attributes << [ k, v ] #else #send("#{k}=", v) #end #end #assign_multiparameter_attributes(multi_parameter_attributes) #end # Overrides AR::Persistence#update_attributes # Needed to change it because the AR::Persistence method updated the attributes # method directly. # Ex. # self.attributes = attributes # # That won't work because our dynamic attributes aren't in the attributes field def update_attributes(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. with_transaction_returning_status do attributes.each do |k, v| self.send("#{k}=", v) end save end end # Overrides AR::Persistence#update_attributes! # See DattsRight#update_attributes for the reason why def update_attributes!(attributes) with_transaction_returning_status do attributes.each do |k, v| self.send("#{k}=", v) end save! end end private # Called after validation on update so that dynamic attributes behave # like normal attributes in the fact that the database is not touched # until save is called. def build_dynamic_attributes return if @save_dynamic_attr.nil? # This originally saves things into the "datts" column. #write_attribute_without_dynamic_attributes "datts", @save_dynamic_attr # We save things into the datts table @save_dynamic_attr.each do |k, v| k = k.to_s datt = datts.find_by_attr_key(k) #puts "to create or to update #{k}?" if datt datt.update_attribute(:value, v) #puts "updated: #{datt.inspect}" #else #datt = datts.create! :name => k, :attr_key => k, :value => v #puts "created: #{datt.inspect} among these datts: #{datts.inspect}" end end @save_dynamic_attr = {} true end # Implements dynamic-attributes as if real getter/setter methods # were defined. def method_missing(method_id, *args, &block) #puts "in method_missing_without_dynamic_attributes. Checking #{method_id}" begin super method_id, *args, &block rescue NoMethodError => e attr_name = method_id.to_s.sub(/\=$/, '') #puts "Now we check to see if price is a dynamic attribute. dynamic_columns: #{dynamic_columns}. is #{attr_name} in those dynamic_columns? #{dynamic_attribute?(attr_name)}" if dynamic_attribute?(attr_name) #puts "== And #{method_id} is a dynamic method" if method_id.to_s =~ /\=$/ return write_attribute(attr_name, args[0]) else return read_attribute(attr_name) end end raise e end end # Overrides AR::Base#read_attribute def read_attribute(attr_name) attr_name = attr_name.to_s if dynamic_attribute?(attr_name) #puts "trying to read datt #{attr_name}" # If value is in the @save_dynamic_attr cache, and it's not blank if !@save_dynamic_attr.blank? and @save_dynamic_attr[attr_name] #puts "datt #{attr_name} IS in cache, so let's return its value" # Then we return the value return @save_dynamic_attr[attr_name] else # The value is not there, or it's blank #puts "datt #{attr_name} is NOT in cache, so let's load it from the datt table" #attrs = read_attribute_without_dynamic_attributes(dynamic_attributes_options[:column_name].to_s) #puts "Here are ALL the datts: #{Datt.all.inspect} and my id is: #{id}" #puts "Here are my the datts: #{datts.inspect} and my id is: #{id}. and the first datt's value is #{datts.first.value}" datt = datts.find_by_attr_key(attr_name) #puts "This is the dat I find: #{datt.inspect}" return datt.try(:value) #attrs = attrs.nil? ? nil : YAML.load(attrs).symbolize_keys! unless attrs.is_a? Hash #return nil if attrs.blank? #return attrs[attr_name.to_sym] end end super(attr_name) end # Overrides AR::Base#write_attribute def write_attribute(attr_name, value) #puts "attempting to write: #{attr_name} = #{value}" if dynamic_attribute?(attr_name) #puts "#{attr_name} is a dynamic_attribute" attr_name = attr_name.to_s @save_dynamic_attr ||= {} # create the cache if needed return @save_dynamic_attr[attr_name] = value end super(attr_name, value) end end end