module DattsRight module InstanceMethods def add_dynamic_attribute(name, klass) key = name.to_s unless attributes.keys.include?(key) dynamic_attribute = dynamic_attributes.find_by_attr_key(key.underscore) unless dynamic_attribute dynamic_attribute = dynamic_attributes.create :name => key, :attr_key => key.underscore, :object_type => klass dynamic_columns(true) end #puts "Just added #{key} to #{self.class.name}##{id}, and here are the dynamic_attributes: #{dynamic_columns.inspect}" dynamic_attribute else false end end def remove_dynamic_attribute(name) name = name.to_s dynamic_attribute = dynamic_attributes.find_by_attr_key(name) if dynamic_attribute dynamic_attribute.destroy dynamic_columns(true) end end # Works like ActiveRecord's attributes except it returns dynamic attributes only #def dynamic_attributes(reload=false) #puts "dynamic_attributes called with reload? #{reload}" #@dynamic_attributes ||= {} #return @dynamic_attributes if !reload ##puts "Reloading dynamic_attributes" #dynamic_attributes.each do |dynamic_attribute| ##puts "Adding this to @dynamic_attributes: #{dynamic_attribute.attr_key} => #{dynamic_attribute.value}" #@dynamic_attributes.merge({dynamic_attribute.attr_key => dynamic_attribute.value}) #end ##puts "Here are teh dynamic_attributes: #{dynamic_attributes.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 #{dynamic_attributes.count} dynamic_attributes" @dynamic_columns={} dynamic_attributes.reload dynamic_attributes.each do |dynamic_attribute| #puts "Going through dynamic_attribute##{dynamic_attribute.id}" dynamic_column = { dynamic_attribute.attr_key.to_sym => { :object_type => dynamic_attribute.object_type, :value => dynamic_attribute.value } } #puts "Added #{dynamic_column.inspect} 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}). dynamic_attributes: #{dynamic_attributes.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 def update_dynamic_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.symbolize_keys.each do |k, v| self.write_dynamic_attribute(k, v) end save end end def update_dynamic_attributes!(attributes) with_transaction_returning_status do attributes.symbolize_keys.each do |k, v| self.write_dynamic_attribute(k, v) end save! end end # Overrides AR::Base#read_attribute def read_dynamic_attribute(attr_name) attr_name = attr_name.to_s if dynamic_attribute?(attr_name) #puts "trying to read dynamic_attribute #{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 "dynamic_attribute #{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 "dynamic_attribute #{attr_name} is NOT in cache, so let's load it from the dynamic_attribute table" #attrs = read_attribute_without_dynamic_attributes(dynamic_attributes_options[:column_name].to_s) #puts "Here are ALL the dynamic_attributes: #{Datt.all.inspect} and my id is: #{id}" #puts "Here are my the dynamic_attributes: #{dynamic_attributes.inspect} and my id is: #{id}. and the first dynamic_attribute's value is #{dynamic_attributes.first.value}" dynamic_attribute = dynamic_attributes.find_by_attr_key(attr_name) #puts "This is the dat I find: #{dynamic_attribute.inspect}" return dynamic_attribute.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_dynamic_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 return nil 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 "dynamic_attributes" column. #write_attribute_without_dynamic_attributes "dynamic_attributes", @save_dynamic_attr # We save things into the dynamic_attributes table @save_dynamic_attr.each do |k, v| k = k.to_s dynamic_attribute = dynamic_attributes.find_by_attr_key(k) #puts "to create or to update #{k}?" if dynamic_attribute dynamic_attribute.update_attribute(:value, v) #puts "updated: #{dynamic_attribute.inspect}" #else #dynamic_attribute = dynamic_attributes.create! :name => k, :attr_key => k, :value => v #puts "created: #{dynamic_attribute.inspect} among these dynamic_attributes: #{dynamic_attributes.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 DattsRight::InstanceMethods#method_missing with method_id #{method_id}" #begin #super method_id, *args, &block #rescue NoMethodError => e #puts "AR doesn't have #{method_id} so DattsRight will take care of it instead" #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 alias :add_datt :add_dynamic_attribute alias :remove_datt :remove_dynamic_attribute alias :read_datt :read_dynamic_attribute alias :write_datt :write_dynamic_attribute alias :update_dynamic_attribute :write_dynamic_attribute end end