|
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 |
| 37 | + |
| 38 | + # (see #updated_at) |
| 39 | + attr_writer :updated_at |
8 | 40 |
|
9 |
| - attr_reader :attributes, :errors |
| 41 | + # The only way to change the attributes of an instance is to directly mutate the attributes. |
| 42 | + # @example |
| 43 | + # |
| 44 | + # model.attributes[:foo] = :bar |
| 45 | + # @return [Hash] |
| 46 | + attr_reader :attributes |
10 | 47 |
|
| 48 | + # @param attributes [Hash] |
11 | 49 | def initialize(attributes = {})
|
12 |
| - @attributes = attributes && attributes.symbolize_keys |
| 50 | + attributes ||= {} # protect against nil |
| 51 | + @attributes = attributes.symbolize_keys.with_indifferent_access |
13 | 52 | @errors = ActiveModel::Errors.new(self)
|
14 | 53 | super
|
15 | 54 | end
|
16 | 55 |
|
17 | 56 | # Defaults to the downcased model name.
|
| 57 | + # This probably isn't a good default, since it's not a unique instance identifier, |
| 58 | + # but that's what is currently implemented \_('-')_/. |
| 59 | + # |
| 60 | + # @note Though +id+ is defined, it will only show up |
| 61 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 62 | + # such as <tt>attributes[:id] = 5</tt>. |
| 63 | + # @return [String, Numeric, Symbol] |
18 | 64 | def id
|
19 |
| - attributes.fetch(:id) { self.class.name.downcase } |
20 |
| - end |
21 |
| - |
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')}" } |
| 65 | + attributes.fetch(:id) do |
| 66 | + defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase |
| 67 | + end |
25 | 68 | end
|
26 | 69 |
|
27 |
| - # Defaults to the time the serializer file was modified. |
| 70 | + # When not set, defaults to the time the file was modified. |
| 71 | + # |
| 72 | + # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up |
| 73 | + # in +attributes+ when it is passed in to the initializer or added to +attributes+, |
| 74 | + # such as <tt>attributes[:updated_at] = Time.current</tt>. |
| 75 | + # @return [String, Numeric, Time] |
28 | 76 | def updated_at
|
29 |
| - attributes.fetch(:updated_at) { File.mtime(__FILE__) } |
| 77 | + attributes.fetch(:updated_at) do |
| 78 | + defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) |
| 79 | + end |
30 | 80 | end
|
31 | 81 |
|
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 |
| 82 | + # To customize model behavior, this method must be redefined. However, |
| 83 | + # there are other ways of setting the +cache_key+ a serializer uses. |
| 84 | + # @return [String] |
| 85 | + def cache_key |
| 86 | + ActiveSupport::Cache.expand_cache_key([ |
| 87 | + self.class.model_name.name.downcase, |
| 88 | + "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" |
| 89 | + ].compact) |
38 | 90 | end
|
39 | 91 |
|
40 | 92 | # The following methods are needed to be minimally implemented for ActiveModel::Errors
|
|
0 commit comments