module FlexColumns::HasFlexColumns
HasFlexColumns
is the module that gets included in an ActiveRecord
model class as soon as it declares a flex column (using FlexColumns::ActiveRecord::Base#flex_column). While most of the actual work of maintaining and working with a flex column is accomplished by the FlexColumns::Definition::FlexColumnContentsClass
module and the FlexColumns::Contents::FlexColumnContentsBase
class, there remains, nevertheless, some important work to do here.
Public Instance Methods
Returns the correct flex-column object for the given column name. This simply creates an instance of the appropriate flex-column class, and saves it away so it will be returned again if someone requests the object for the same column later.
This method almost never gets invoked directly; instead, everything calls it via FlexColumns::ActiveRecord::Base
#_flex_column_object_for.
# File lib/flex_columns/has_flex_columns.rb, line 55 def _flex_column_owned_object_for(column_name, create_if_needed = true) column_name = self.class._flex_column_normalize_name(column_name) out = _flex_column_objects[column_name] if (! out) && create_if_needed out = _flex_column_objects[column_name] = self.class._flex_column_class_for(column_name).new(self) end out end
Before we save this model, make sure each flex column has a chance to serialize itself up and assign itself properly to this model object. Note that we only need to call through to flex-column objects that have actually been instantiated, since, by definition, there’s no way the contents of any other flex columns could possibly have been changed.
# File lib/flex_columns/has_flex_columns.rb, line 30 def _flex_columns_before_save! self.class._all_flex_column_names.each do |flex_column_name| klass = self.class._flex_column_class_for(flex_column_name) if klass.requires_serialization_on_save?(self) _flex_column_object_for(flex_column_name).before_save! end end end
Before we validate this model, make sure each flex column has a chance to run its validations and propagate any errors back to this model. Note that we need to call through to any flex-column object that has a validation defined, since we want to comply with Rails’ validation strategy: validations run whenever you save an object, whether you’ve changed that particular attribute or not.
# File lib/flex_columns/has_flex_columns.rb, line 43 def _flex_columns_before_validation! _all_present_flex_column_objects.each do |flex_column_object| flex_column_object.before_validation! end end
See above. We’re actually overriding both methods, because read_attribute_for_serialization
doesn’t exist in ActiveRecord
3.0.x.
# File lib/flex_columns/has_flex_columns.rb, line 82 def as_json(options = { }) flex_column_names = self.class._all_flex_column_names out = super(:except => flex_column_names) flex_columns_hash = { } flex_column_names.each do |column_name| hash = _flex_column_object_for(column_name).to_hash_for_serialization flex_columns_hash[column_name] = hash unless hash.empty? end if include_root_in_json && out.keys.length == 1 out[out.keys.first].merge!(flex_columns_hash) else out.merge!(flex_columns_hash) end out end
This little-know ActiveRecord
method gets called to produce a string for inspect for a particular attribute. Because the default implementation uses read_attribute, if we don’t override it, it will simply return our actual string in the database; if this is compressed data, this is meaningless to a programmer. So we override this to instead deserialize the column and call inspect on the actual FlexColumnContentsBase object, which shows interesting information.
NOTE: See the warning comment above FlexColumnContentsBase#inspect, which points out that this will deserialize the column if it hasn’t already – so calling this has a performance penalty. This should be fine, since calling inspect in bulk isn’t something a program should be doing in production mode anyway, but it’s worth noting.
# File lib/flex_columns/has_flex_columns.rb, line 118 def attribute_for_inspect(attr_name) cn = self.class._all_flex_column_names if cn.include?(attr_name.to_sym) _flex_column_object_for(attr_name).inspect else super(attr_name) end end
When ActiveRecord
serializes an entire ActiveRecord
object (for example, if you call to_json on it), it reads each column individually using this method – which, in the default implementation, just calls send. (Well, it’s actually aliased to send, but it has the same effect.)
However, if you’re serializing an ActiveRecord
model that contains a flex column, you almost certainly just want that to behave as if the flex_column is a Hash, and serialize it that way. So we override it to do just that right here.
# File lib/flex_columns/has_flex_columns.rb, line 72 def read_attribute_for_serialization(attribute_name) if self.class._has_flex_column_named?(attribute_name) _flex_column_object_for(attribute_name).to_hash_for_serialization else super(attribute_name) end end
When you reload a model object, we should reload its flex-column objects, too.
# File lib/flex_columns/has_flex_columns.rb, line 102 def reload(*args) out = super(*args) @_flex_column_objects = { } out end
Private Instance Methods
Returns all flex-column objects that have been instantiated – that is, any flex-column object that anybody has asked for yet.
# File lib/flex_columns/has_flex_columns.rb, line 135 def _all_present_flex_column_objects _flex_column_objects.values end
Returns the Hash that we keep flex-column objects in, indexed by column name.
# File lib/flex_columns/has_flex_columns.rb, line 129 def _flex_column_objects @_flex_column_objects ||= { } end