|
1 | | -# ActiveModelSerializers::Model is a convenient |
2 | | -# serializable class to inherit from when making |
3 | | -# serializable non-activerecord objects. |
| 1 | +# ActiveModelSerializers::Model is a convenient superclass when making serializable non-activerecord objects. |
| 2 | +# It also serves as documentation of an implementation that satisfies ActiveModel::Serializer::Lint::Tests. |
4 | 3 | module ActiveModelSerializers |
5 | 4 | class Model |
6 | | - include ActiveModel::Model |
7 | 5 | include ActiveModel::Serializers::JSON |
| 6 | + include ActiveModel::Model |
8 | 7 |
|
9 | | - attr_reader :attributes, :errors |
| 8 | + # @method :attributes |
| 9 | + # - the attributes hash returned by `#attributes` will always be the value of the attributes passed in at initialization, |
| 10 | + # regardless of what attr_* methods were defined. The attributes are modified |
| 11 | + # - unset attributes, rather than being nil, will not be included in the attributes hash, even when an `attr_accessor` exists. |
| 12 | + # - the only way to change the change the attributes after initialization is to mutate the `attributes` directly. Accessor methods |
| 13 | + # will NOT mutate the attributes. (This is a bug). |
| 14 | + # |
| 15 | + # For now, the Model only supports the notion of 'attributes'. In the tests, we do support 'associations' on the PORO, |
| 16 | + # in order to adds accessors for values that should not appear in the attributes hash, as we model associations. |
| 17 | + # However, it is not yet clear if it makes sense for a PORO to have associations outside of the tests. |
| 18 | + def self.attributes(*names) |
| 19 | + # Silence redefinition of methods warnings |
| 20 | + ActiveModelSerializers.silence_warnings do |
| 21 | + attr_accessor(*names) |
| 22 | + end |
| 23 | + end |
| 24 | + |
| 25 | + # Support for validation and other ActiveModel::Errors |
| 26 | + attr_reader :errors |
| 27 | + |
| 28 | + attr_writer :updated_at |
10 | 29 |
|
11 | 30 | def initialize(attributes = {}) |
12 | | - @attributes = attributes && attributes.symbolize_keys |
| 31 | + attributes ||= {} # protect against nil |
| 32 | + @attributes = attributes.symbolize_keys.with_indifferent_access |
13 | 33 | @errors = ActiveModel::Errors.new(self) |
14 | 34 | super |
15 | 35 | end |
16 | 36 |
|
17 | | - # Defaults to the downcased model name. |
18 | | - def id |
19 | | - attributes.fetch(:id) { self.class.name.downcase } |
| 37 | + # The only way to change the attributes of an instance is to directly mutate the attributes. |
| 38 | + # |
| 39 | + # model.attributes[:foo] = :bar |
| 40 | + def attributes |
| 41 | + instance_variable_get(:@attributes) |
20 | 42 | end |
21 | 43 |
|
22 | | - # Defaults to the downcased model name and updated_at |
23 | | - def cache_key |
24 | | - attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" } |
| 44 | + # Defaults to the downcased model name. |
| 45 | + # This probably isn't a good default, since it's not a unique instance identifier, |
| 46 | + # but that's what is currently implemented \_('-')_/ |
| 47 | + # NOTE that though +id+ is defined, it will only show up |
| 48 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 49 | + # such as <tt>attributes[:id] = 5</tt>. |
| 50 | + def id |
| 51 | + instance_variable_get(:@attributes).fetch(:id) { self.class.model_name.name && self.class.model_name.name.downcase } |
25 | 52 | end |
26 | 53 |
|
27 | | - # Defaults to the time the serializer file was modified. |
| 54 | + # When not set, defaults to the time the file was modified. |
| 55 | + # NOTE that though +updated_at+ and +updated_at=+ are defined, it will only show up |
| 56 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 57 | + # such as <tt>attributes[:updated_at] = Time.current</tt>. |
28 | 58 | def updated_at |
29 | | - attributes.fetch(:updated_at) { File.mtime(__FILE__) } |
| 59 | + defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) |
30 | 60 | end |
31 | 61 |
|
32 | | - def read_attribute_for_serialization(key) |
33 | | - if key == :id || key == 'id' |
34 | | - attributes.fetch(key) { id } |
35 | | - else |
36 | | - attributes[key] |
37 | | - end |
| 62 | + # To customize model behavior, this method must be redefined. However, |
| 63 | + # there are other ways of setting the +cache_key+ a serializer uses. |
| 64 | + def cache_key |
| 65 | + ActiveSupport::Cache.expand_cache_key([ |
| 66 | + self.class.model_name.name.downcase, |
| 67 | + "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" |
| 68 | + ].compact) |
38 | 69 | end |
39 | 70 |
|
40 | 71 | # The following methods are needed to be minimally implemented for ActiveModel::Errors |
|
0 commit comments