|
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 for making your models |
| 2 | +# from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation |
| 3 | +# that satisfies ActiveModel::Serializer::Lint::Tests. |
4 | 4 | module ActiveModelSerializers |
5 | 5 | class Model |
6 | | - include ActiveModel::Model |
7 | 6 | include ActiveModel::Serializers::JSON |
| 7 | + include ActiveModel::Model |
| 8 | + |
| 9 | + # Easily declare instance attributes with setters and getters for each. |
| 10 | + # |
| 11 | + # All attributes to initialize an instance must have setters. |
| 12 | + # However, the hash turned by +attributes+ instance method will ALWAYS |
| 13 | + # be the value of the initial attributes, regardless of what accessors are defined. |
| 14 | + # The only way to change the change the attributes after initialization is |
| 15 | + # to mutate the +attributes+ directly. |
| 16 | + # Accessor methods do NOT mutate the attributes. (This is a bug). |
| 17 | + # |
| 18 | + # @note For now, the Model only supports the notion of 'attributes'. |
| 19 | + # In the tests, there is a special Model that also supports 'associations'. This is |
| 20 | + # important so that we can add accessors for values that should not appear in the |
| 21 | + # attributes hash when modeling associations. It is not yet clear if it |
| 22 | + # makes sense for a PORO to have associations outside of the tests. |
| 23 | + # |
| 24 | + # @overload attributes(names) |
| 25 | + # @param names [Array<String, Symbol>] |
| 26 | + # @param name [String, Symbol] |
| 27 | + def self.attributes(*names) |
| 28 | + # Silence redefinition of methods warnings |
| 29 | + ActiveModelSerializers.silence_warnings do |
| 30 | + attr_accessor(*names) |
| 31 | + end |
| 32 | + end |
| 33 | + |
| 34 | + # Support for validation and other ActiveModel::Errors |
| 35 | + # @return [ActiveModel::Errors] |
| 36 | + attr_reader :errors |
8 | 37 |
|
9 | | - attr_reader :attributes, :errors |
| 38 | + # (see #updated_at) |
| 39 | + attr_writer :updated_at |
10 | 40 |
|
| 41 | + # @param attributes [Hash] |
11 | 42 | def initialize(attributes = {}) |
12 | | - @attributes = attributes && attributes.symbolize_keys |
| 43 | + attributes ||= {} # protect against nil |
| 44 | + @attributes = attributes.symbolize_keys.with_indifferent_access |
13 | 45 | @errors = ActiveModel::Errors.new(self) |
14 | 46 | super |
15 | 47 | end |
16 | 48 |
|
17 | | - # Defaults to the downcased model name. |
18 | | - def id |
19 | | - attributes.fetch(:id) { self.class.name.downcase } |
| 49 | + # The only way to change the attributes of an instance is to directly mutate the attributes. |
| 50 | + # @example |
| 51 | + # |
| 52 | + # model.attributes[:foo] = :bar |
| 53 | + # @return [Hash] |
| 54 | + def attributes |
| 55 | + instance_variable_get(:@attributes) |
20 | 56 | end |
21 | 57 |
|
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')}" } |
| 58 | + # Defaults to the downcased model name. |
| 59 | + # This probably isn't a good default, since it's not a unique instance identifier, |
| 60 | + # but that's what is currently implemented \_('-')_/. |
| 61 | + # |
| 62 | + # @note Though +id+ is defined, it will only show up |
| 63 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 64 | + # such as <tt>attributes[:id] = 5</tt>. |
| 65 | + # @return [String, Numeric, Symbol] |
| 66 | + def id |
| 67 | + attributes.fetch(:id) do |
| 68 | + defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase |
| 69 | + end |
25 | 70 | end |
26 | 71 |
|
27 | | - # Defaults to the time the serializer file was modified. |
| 72 | + # When not set, defaults to the time the file was modified. |
| 73 | + # |
| 74 | + # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up |
| 75 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 76 | + # such as <tt>attributes[:updated_at] = Time.current</tt>. |
| 77 | + # @return [String, Numeric, Time] |
28 | 78 | def updated_at |
29 | | - attributes.fetch(:updated_at) { File.mtime(__FILE__) } |
| 79 | + attributes.fetch(:updated_at) do |
| 80 | + defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) |
| 81 | + end |
30 | 82 | end |
31 | 83 |
|
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 |
| 84 | + # To customize model behavior, this method must be redefined. However, |
| 85 | + # there are other ways of setting the +cache_key+ a serializer uses. |
| 86 | + # @return [String] |
| 87 | + def cache_key |
| 88 | + ActiveSupport::Cache.expand_cache_key([ |
| 89 | + self.class.model_name.name.downcase, |
| 90 | + "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" |
| 91 | + ].compact) |
38 | 92 | end |
39 | 93 |
|
40 | 94 | # The following methods are needed to be minimally implemented for ActiveModel::Errors |
|
0 commit comments