Module: FlexColumns::HasFlexColumns

Extended by:
ActiveSupport::Concern
Defined in:
lib/flex_columns/has_flex_columns.rb

Overview

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.

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#_flex_column_owned_object_for(column_name, create_if_needed = true) ⇒ Object

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.



55
56
57
58
59
60
61
62
63
# 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

#_flex_columns_before_save!Object

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.



30
31
32
33
34
35
36
37
# 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

#_flex_columns_before_validation!Object

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.



43
44
45
46
47
# 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

#as_json(options = { }) ⇒ Object

See above. We’re actually overriding both methods, because #read_attribute_for_serialization doesn’t exist in ActiveRecord 3.0.x.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# 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

#attribute_for_inspect(attr_name) ⇒ Object

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.



118
119
120
121
122
123
124
125
# 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

#read_attribute_for_serialization(attribute_name) ⇒ Object

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.



72
73
74
75
76
77
78
# 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

#reload(*args) ⇒ Object

When you reload a model object, we should reload its flex-column objects, too.



102
103
104
105
106
# File 'lib/flex_columns/has_flex_columns.rb', line 102

def reload(*args)
  out = super(*args)
  @_flex_column_objects = { }
  out
end