File: model.rb

package info (click to toggle)
ruby-active-model-serializers 0.10.12-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,752 kB
  • sloc: ruby: 13,138; sh: 53; makefile: 6
file content (132 lines) | stat: -rw-r--r-- 5,333 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true

# ActiveModelSerializers::Model is a convenient superclass for making your models
# from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
# that satisfies ActiveModel::Serializer::Lint::Tests.
require 'active_support/core_ext/hash'
module ActiveModelSerializers
  class Model
    include ActiveModel::Serializers::JSON
    include ActiveModel::Model

    # Declare names of attributes to be included in +attributes+ hash.
    # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
    # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
    #
    # @overload attribute_names
    #   @return [Array<Symbol>]
    class_attribute :attribute_names, instance_writer: false, instance_reader: false
    # Initialize +attribute_names+ for all subclasses.  The array is usually
    # mutated in the +attributes+ method, but can be set directly, as well.
    self.attribute_names = []

    # Easily declare instance attributes with setters and getters for each.
    #
    # To initialize an instance, all attributes must have setters.
    # However, the hash returned by +attributes+ instance method will ALWAYS
    # be the value of the initial attributes, regardless of what accessors are defined.
    # The only way to change the change the attributes after initialization is
    # to mutate the +attributes+ directly.
    # Accessor methods do NOT mutate the attributes.  (This is a bug).
    #
    # @note For now, the Model only supports the notion of 'attributes'.
    #   In the tests, there is a special Model that also supports 'associations'. This is
    #   important so that we can add accessors for values that should not appear in the
    #   attributes hash when modeling associations. It is not yet clear if it
    #   makes sense for a PORO to have associations outside of the tests.
    #
    # @overload attributes(names)
    #   @param names [Array<String, Symbol>]
    #   @param name [String, Symbol]
    def self.attributes(*names)
      self.attribute_names |= names.map(&:to_sym)
      # Silence redefinition of methods warnings
      ActiveModelSerializers.silence_warnings do
        attr_accessor(*names)
      end
    end

    # Opt-in to breaking change
    def self.derive_attributes_from_names_and_fix_accessors
      unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
        prepend(DeriveAttributesFromNamesAndFixAccessors)
      end
    end

    module DeriveAttributesFromNamesAndFixAccessors
      def self.included(base)
        # NOTE that +id+ will always be in +attributes+.
        base.attributes :id
      end

      # Override the +attributes+ method so that the hash is derived from +attribute_names+.
      #
      # The fields in +attribute_names+ determines the returned hash.
      # +attributes+ are returned frozen to prevent any expectations that mutation affects
      # the actual values in the model.
      def attributes
        self.class.attribute_names.each_with_object({}) do |attribute_name, result|
          result[attribute_name] = public_send(attribute_name).freeze
        end.with_indifferent_access.freeze
      end
    end

    # Support for validation and other ActiveModel::Errors
    # @return [ActiveModel::Errors]
    attr_reader :errors

    # (see #updated_at)
    attr_writer :updated_at

    # The only way to change the attributes of an instance is to directly mutate the attributes.
    # @example
    #
    #   model.attributes[:foo] = :bar
    # @return [Hash]
    attr_reader :attributes

    # @param attributes [Hash]
    def initialize(attributes = {})
      attributes ||= {} # protect against nil
      @attributes = attributes.symbolize_keys.with_indifferent_access
      @errors = ActiveModel::Errors.new(self)
      super
    end

    # Defaults to the downcased model name.
    # This probably isn't a good default, since it's not a unique instance identifier,
    # but that's what is currently implemented \_('-')_/.
    #
    # @note Though +id+ is defined, it will only show up
    #   in +attributes+ when it is passed in to the initializer or added to +attributes+,
    #   such as <tt>attributes[:id] = 5</tt>.
    # @return [String, Numeric, Symbol]
    def id
      attributes.fetch(:id) do
        defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
      end
    end

    # When not set, defaults to the time the file was modified.
    #
    # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
    #   in +attributes+ when it is passed in to the initializer or added to +attributes+,
    #   such as <tt>attributes[:updated_at] = Time.current</tt>.
    # @return [String, Numeric, Time]
    def updated_at
      attributes.fetch(:updated_at) do
        defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
      end
    end

    # To customize model behavior, this method must be redefined. However,
    # there are other ways of setting the +cache_key+ a serializer uses.
    # @return [String]
    def cache_key
      ActiveSupport::Cache.expand_cache_key([
        self.class.model_name.name.downcase,
        "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
      ].compact)
    end
  end
end