Class: AttrJson::AttributeDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/attr_json/attribute_definition.rb,
lib/attr_json/attribute_definition/registry.rb

Overview

Represents a attr_json definition, on either a AttrJson::Record or AttrJson::Model. Normally this class is only used by AttrJson::AttributeDefinition::Registry.

Defined Under Namespace

Classes: Registry

Constant Summary collapse

NO_DEFAULT_PROVIDED =
Object.new.freeze
VALID_OPTIONS =
%i{container_attribute store_key default array}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, type, options = {}) ⇒ AttributeDefinition

Returns a new instance of AttributeDefinition.

Parameters:

  • name (Symbol, String)
  • type (Symbol, ActiveModel::Type::Value)

    Symbol is looked up in ActiveRecord::Type.lookup, but with adapter: nil for no custom adapter-specific lookup.

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • store_key (Symbol, String)
  • container_attribute (Symbol, ActiveModel::Type::Value)

    Only means something in a AttrJson::Record, no meaning in a AttrJson::Model.

  • default (Object, Symbol, Proc) — default: nil
  • array (Boolean) — default: false


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/attr_json/attribute_definition.rb', line 26

def initialize(name, type, options = {})
  options.assert_valid_keys *VALID_OPTIONS
  # saving original args for reflection useful for debugging, maybe other things.
  @original_args = [name, type, options]

  @name = name.to_sym

  @container_attribute = options[:container_attribute] && options[:container_attribute].to_s

  @store_key = options[:store_key] && options[:store_key].to_s

  @default = if options.has_key?(:default)
    options[:default]
  elsif options[:array] == true
    -> { [] }
  else
    NO_DEFAULT_PROVIDED
  end

  type = self.class.lookup_type(type)

  @type = (options[:array] == true ? AttrJson::Type::Array.new(type) : type)
end

Instance Attribute Details

#container_attributeObject (readonly)

Returns the value of attribute container_attribute.



14
15
16
# File 'lib/attr_json/attribute_definition.rb', line 14

def container_attribute
  @container_attribute
end

#nameObject (readonly)

Returns the value of attribute name.



14
15
16
# File 'lib/attr_json/attribute_definition.rb', line 14

def name
  @name
end

#original_argsObject (readonly)

Returns the value of attribute original_args.



14
15
16
# File 'lib/attr_json/attribute_definition.rb', line 14

def original_args
  @original_args
end

#typeObject (readonly)

Returns the value of attribute type.



14
15
16
# File 'lib/attr_json/attribute_definition.rb', line 14

def type
  @type
end

Class Method Details

.lookup_type(type) ⇒ Object

Can look up a symbol to a type, or leave a type alone, or raise if it's neither. Extracted into a method, so it can be called from AttrJson::Model#attr_json, for some timezone aware shenanigans.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/attr_json/attribute_definition.rb', line 118

def self.lookup_type(type)
  if type.is_a? Symbol
    # ActiveModel::Type.lookup may make more sense, but ActiveModel::Type::Date
    # seems to have a bug with multi-param assignment. Mostly they return
    # the same types, but ActiveRecord::Type::Date works with multi-param assignment.
    #
    # We pass `adapter: nil` to avoid triggering a db connection.
    # See: https://github.com/jrochkind/attr_json/issues/41
    # This is at the "cost" of not using any adapter-specific types... which
    # maybe preferable anyway?
    #
    # AND we add precision for datetime/time types... since we're using Rails json
    # serializing, we're kind of stuck with this precision in current implementation.
    lookup_kwargs = { adapter: nil }
    if type == :datetime || type == :time
      lookup_kwargs = { precision: ActiveSupport::JSON::Encoding.time_precision }
    end

    type = ActiveRecord::Type.lookup(type, **lookup_kwargs)
  elsif !(type.is_a?(ActiveModel::Type::Value) || type.is_a?(ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter))
    raise ArgumentError, "Second argument (#{type}) must be a symbol or instance of an ActiveModel::Type::Value subclass"
  end
  type
end

.single_model_type?(arg_type) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
# File 'lib/attr_json/attribute_definition.rb', line 105

def self.single_model_type?(arg_type)
  arg_type.is_a?(AttrJson::Type::Model) || arg_type.is_a?(AttrJson::Type::PolymorphicModel)
end

Instance Method Details

#array_of_primitive_type?Boolean

Used for figuring out appropriate behavior in nested attribute implementation among other places. true if type is an array of things that are not nested models.

Returns:

  • (Boolean)


111
112
113
# File 'lib/attr_json/attribute_definition.rb', line 111

def array_of_primitive_type?
  array_type? && !self.class.single_model_type?(type.base_type)
end

#array_type?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/attr_json/attribute_definition.rb', line 95

def array_type?
  type.is_a? AttrJson::Type::Array
end

#cast(value) ⇒ Object



50
51
52
# File 'lib/attr_json/attribute_definition.rb', line 50

def cast(value)
  type.cast(value)
end

#default_argumentObject

Can be value or proc!



75
76
77
78
# File 'lib/attr_json/attribute_definition.rb', line 75

def default_argument
  return nil unless has_default?
  @default
end

#deserialize(value) ⇒ Object



58
59
60
# File 'lib/attr_json/attribute_definition.rb', line 58

def deserialize(value)
  type.deserialize(value)
end

#has_custom_store_key?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/attr_json/attribute_definition.rb', line 62

def has_custom_store_key?
  !!@store_key
end

#has_default?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/attr_json/attribute_definition.rb', line 70

def has_default?
  @default != NO_DEFAULT_PROVIDED
end

#provide_default!Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/attr_json/attribute_definition.rb', line 80

def provide_default!
  unless has_default?
    raise ArgumentError.new("This #{self.class.name} does not have a default defined!")
  end

  # Seems weird to assume a Proc can't be the default itself, but I guess
  # Proc's aren't serializable, so fine assumption. Modeled after:
  # https://github.com/rails/rails/blob/f2dfd5c6fdffdf65e6f07aae8e855ac802f9302f/activerecord/lib/active_record/attribute/user_provided_default.rb#L12-L16
  if @default.is_a?(Proc)
    cast(@default.call)
  else
    cast(@default)
  end
end

#serialize(value) ⇒ Object



54
55
56
# File 'lib/attr_json/attribute_definition.rb', line 54

def serialize(value)
  type.serialize(value)
end

#single_model_type?Boolean

Used for figuring out appropriate behavior for nested attribute implementation among other places. true if type is a nested model, either straight or polymorphic

Returns:

  • (Boolean)


101
102
103
# File 'lib/attr_json/attribute_definition.rb', line 101

def single_model_type?
  self.class.single_model_type?(type)
end

#store_keyObject



66
67
68
# File 'lib/attr_json/attribute_definition.rb', line 66

def store_key
  AttrJson.efficient_to_s(@store_key || name)
end