module DattsRight module InstanceMethods def add_dynamic_attribute(name, object_type, value=nil) key = name.to_s.underscore return false if self.class.columns_hash[key] # if key already exists as a normal attribute unless dynamic_attribute?(key) new_dynamic_attribute = dynamic_attributes.new :name => name, :attr_key => key, :object_type => object_type, "#{object_type}_value".to_sym => value @dynamic_attributes_cache[key.to_sym] = new_dynamic_attribute return new_dynamic_attribute end return false end def add_dynamic_attribute!(name, object_type, value=nil) key = name.to_s.underscore return false if self.class.columns_hash[key] # if key already exists as a normal attribute unless dynamic_attribute?(key) new_dynamic_attribute = dynamic_attributes.create! :name => name, :attr_key => key, :object_type => object_type, "#{object_type}_value".to_sym => value @dynamic_attributes_cache[key.to_sym] = new_dynamic_attribute return new_dynamic_attribute end return false end def remove_dynamic_attribute(name) # Remove from the cache @dynamic_attributes_cache.delete(name.to_sym) # Then remove from the db dynamic_attribute = dynamic_attributes.find_by_attr_key(name.to_s) dynamic_attribute.destroy if dynamic_attribute end # Give users access to the cache def dynamic_attribute_details(key) @dynamic_attributes_cache[key] end # Determines if the given attribute is a dynamic attribute. def dynamic_attribute?(attr) !@dynamic_attributes_cache[attr.to_sym].nil? 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 # Like AR::Base#read_attribute def read_dynamic_attribute(attr_name) attr_name = attr_name.to_sym if dynamic_attribute?(attr_name) #puts "Reading #{attr_name}. The whole cache: #{@dynamic_attributes_cache.inspect}" @dynamic_attributes_cache[attr_name].value end end # Like AR::Base#write_attribute def write_dynamic_attribute(attr_name, value) #puts "attempting to write: #{attr_name} = #{value}" attr_name = attr_name.to_sym if dynamic_attribute?(attr_name) #puts "#{attr_name} is a dynamic_attribute" #puts "Writing @dynamic_attributes_cache[:#{attr_name}].value = #{value}" dynamic_attribute = @dynamic_attributes_cache[attr_name] dynamic_attribute.value = value #puts "In write_dynamic_attribute. Full cache: #{@dynamic_attributes_cache.inspect}" return dynamic_attribute.value end end 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 #respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}") begin send("#{k}=", v) #puts "Set super #{k} to #{v}" rescue NoMethodError => e #puts "NoMethodError was raised: #{e}, so we now check to see if '#{k}' is dynamic_attribute" if dynamic_attribute?(k) write_dynamic_attribute("#{k}", v) else raise ActiveRecord::UnknownAttributeError, "unknown attribute: #{k}" end end end end assign_multiparameter_attributes(multi_parameter_attributes) 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 @dynamic_attributes_cache ||= {} @dynamic_attributes_cache.each do |k, v| #puts "about to save: #{v.inspect}" v.save #puts "Just to saved: #{v.inspect}" #dynamic_attribute = dynamic_attributes.find_by_attr_key(k.to_s) #value_column = "#{v[:object_type]}_value".to_sym #if dynamic_attribute #dynamic_attribute.update_attributes value_column => v[:value], :object_type => v[:object_type] #else ##@dynamic_attributes_cache[k] = dynamic_attributes.create! :name => k, :attr_key => k, value_column => v[:value], :object_type => v[:object_type] #@dynamic_attributes_cache[k].save #end end end def cache_dynamic_attributes @dynamic_attributes_cache = {} dynamic_attributes.each do |dynamic_attribute| #puts "Caching: #{dynamic_attribute.inspect}" @dynamic_attributes_cache[dynamic_attribute.attr_key.to_sym] = dynamic_attribute end @dynamic_attributes_cache end def method_missing(method_id, *args, &body) #begin #method_missing_without_dynamic_attributes method_id, *args, &block #rescue NoMethodError => e #attr_name = method_id.to_s.sub(/\=$/, '') #if is_dynamic_attribute?(attr_name) #if method_id.to_s =~ /\=$/ #return write_attribute_with_dynamic_attributes(attr_name, args[0]) #else #return read_attribute_with_dynamic_attributes(attr_name) #end #end #raise e #end begin super(method_id, *args, &body) rescue NoMethodError => e attr_name = method_id.to_s.sub(/\=$/, '') if dynamic_attribute?(attr_name) #puts "#{attr_name} is a dynamic_attribute" method_id.to_s =~ /\=$/ ? write_dynamic_attribute(attr_name, args[0]) : read_dynamic_attribute(attr_name) #else #super(method_id, *args, &body) # send it to super again, so that any magic activerecord needs to do will still work else raise NoMethodError end end end alias :add_datt :add_dynamic_attribute 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 :datt_details :dynamic_attribute_details alias :update_dynamic_attribute :write_dynamic_attribute end end