Module: OptionsModel::Concerns::Attributes::ClassMethods

Defined in:
lib/options_model/concerns/attributes.rb

Instance Method Summary collapse

Instance Method Details

#attribute(name, cast_type, default: nil, array: false) ⇒ Object



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
# File 'lib/options_model/concerns/attributes.rb', line 9

def attribute(name, cast_type, default: nil, array: false)
  check_not_finalized!

  name = name.to_sym
  check_name_validity! name

  ActiveModel::Type.lookup(cast_type)

  attribute_defaults[name] = default
  default_extractor =
    if default.respond_to?(:call)
      ".call"
    elsif default.duplicable?
      ".deep_dup"
    else
      ""
    end

  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def #{name}
      value = attributes[:#{name}]
      return value unless value.nil?
      attributes[:#{name}] = self.class.attribute_defaults[:#{name}]#{default_extractor}
      attributes[:#{name}]
    end
  STR

  if array
    generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
      def #{name}=(value)
        if value.respond_to?(:to_a)
          attributes[:#{name}] = value.to_a.map { |i| ActiveModel::Type.lookup(:#{cast_type}).cast(i) }
        elsif value.nil?
          attributes[:#{name}] = self.class.attribute_defaults[:#{name}]#{default_extractor}
        else
          raise ArgumentError,
                "`value` should respond to `to_a`, but got \#{value.class} -- \#{value.inspect}"
        end
      end
    STR
  else
    generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
      def #{name}=(value)
        attributes[:#{name}] = ActiveModel::Type.lookup(:#{cast_type}).cast(value)
      end
    STR

    generated_attribute_methods.send :alias_method, :"#{name}?", name if cast_type == :boolean
  end

  attribute_names_for_inlining << name

  self
end

#attribute_defaultsObject



128
129
130
# File 'lib/options_model/concerns/attributes.rb', line 128

def attribute_defaults
  @attribute_defaults ||= ActiveSupport::HashWithIndifferentAccess.new
end

#attribute_namesObject



144
145
146
# File 'lib/options_model/concerns/attributes.rb', line 144

def attribute_names
  attribute_names_for_nesting + attribute_names_for_inlining
end

#attribute_names_for_inliningObject



140
141
142
# File 'lib/options_model/concerns/attributes.rb', line 140

def attribute_names_for_inlining
  @attribute_names_for_inlining ||= Set.new
end

#attribute_names_for_nestingObject



136
137
138
# File 'lib/options_model/concerns/attributes.rb', line 136

def attribute_names_for_nesting
  @attribute_names_for_nesting ||= Set.new
end

#embeds_one(name, class_name: nil, anonymous_class: nil) ⇒ Object

Raises:

  • (ArgumentError)


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
# File 'lib/options_model/concerns/attributes.rb', line 87

def embeds_one(name, class_name: nil, anonymous_class: nil)
  check_not_finalized!

  raise ArgumentError, "must provide at least one of `class_name` or `anonymous_class`" if class_name.blank? && anonymous_class.nil?

  name = name.to_sym
  check_name_validity! name

  nested_classes[name] = if class_name.present?
    class_name.constantize
  else
    anonymous_class
  end

  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def #{name}
      nested_attributes[:#{name}] ||= self.class.nested_classes[:#{name}].new(attributes[:#{name}])
    end
  STR

  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def #{name}=(value)
      klass = self.class.nested_classes[:#{name}]
      if value.respond_to?(:to_h)
        nested_attributes[:#{name}] = klass.new(value.to_h)
      elsif value.is_a? klass
        nested_attributes[:#{name}] = value
      elsif value.nil?
        nested_attributes[:#{name}] = klass.new
      else
        raise ArgumentError,
              "`value` should respond to `to_h` or \#{klass}, but got \#{value.class}"
      end
    end
  STR

  attribute_names_for_nesting << name

  self
end

#enum_attribute(name, enum, default: nil, allow_nil: false) ⇒ Object

Raises:

  • (ArgumentError)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/options_model/concerns/attributes.rb', line 64

def enum_attribute(name, enum, default: nil, allow_nil: false)
  check_not_finalized!

  raise ArgumentError, "enum should be an Array and can't empty" unless enum.is_a?(Array) && enum.any?

  enum = enum.map(&:to_s)

  attribute name, :string, default: default

  pluralized_name = name.to_s.pluralize
  generated_class_methods.synchronize do
    generated_class_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
    def #{pluralized_name}
      %w(#{enum.join(' ')}).freeze
    end
    STR

    validates name, inclusion: { in: enum }, allow_nil: allow_nil
  end

  self
end

#finalize!(nested = true) ⇒ Object



152
153
154
155
156
# File 'lib/options_model/concerns/attributes.rb', line 152

def finalize!(nested = true)
  nested_classes.values.each(&:finalize!) if nested

  @finalized = true
end

#finalized?Boolean



148
149
150
# File 'lib/options_model/concerns/attributes.rb', line 148

def finalized?
  @finalized ||= false
end

#nested_classesObject



132
133
134
# File 'lib/options_model/concerns/attributes.rb', line 132

def nested_classes
  @nested_classes ||= ActiveSupport::HashWithIndifferentAccess.new
end