Module: ActiveRecord::Store::ClassMethods

Defined in:
lib/store_attribute/active_record/store.rb

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#store_attribute_register_attributesObject



157
158
159
160
161
162
163
164
165
166
# File 'lib/store_attribute/active_record/store.rb', line 157

def store_attribute_register_attributes
  return @store_attribute_register_attributes if instance_variable_defined?(:@store_attribute_register_attributes)

  @store_attribute_register_attributes =
    if superclass.respond_to?(:store_attribute_register_attributes)
      superclass.store_attribute_register_attributes
    else
      StoreAttribute.store_attribute_register_attributes
    end
end

#store_attribute_unset_values_fallback_to_defaultObject



168
169
170
171
172
173
174
175
176
177
# File 'lib/store_attribute/active_record/store.rb', line 168

def store_attribute_unset_values_fallback_to_default
  return @store_attribute_unset_values_fallback_to_default if instance_variable_defined?(:@store_attribute_unset_values_fallback_to_default)

  @store_attribute_unset_values_fallback_to_default =
    if superclass.respond_to?(:store_attribute_unset_values_fallback_to_default)
      superclass.store_attribute_unset_values_fallback_to_default
    else
      StoreAttribute.store_attribute_unset_values_fallback_to_default
    end
end

Instance Method Details

#_define_predicate_method(name, prefix: nil, suffix: nil) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/store_attribute/active_record/store.rb', line 247

def _define_predicate_method(name, prefix: nil, suffix: nil)
  accessor_prefix =
    case prefix
    when String, Symbol
      "#{prefix}_"
    when TrueClass
      "#{name}_"
    else
      ""
    end
  accessor_suffix =
    case suffix
    when String, Symbol
      "_#{suffix}"
    when TrueClass
      "_#{name}"
    else
      ""
    end

  _store_accessors_module.module_eval do
    name = "#{accessor_prefix}#{name}#{accessor_suffix}"

    define_method("#{name}?") do
      send(name) == true
    end
  end
end

#_define_store_attribute(store_name) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/store_attribute/active_record/store.rb', line 196

def _define_store_attribute(store_name)
  attr_name = store_name.to_s

  defaultik = Type::TypedStore::Defaultik.new

  owner = self

  # Rails >7.1
  if respond_to?(:decorate_attributes)
    decorate_attributes([attr_name]) do |_, subtype|
      subtypes = _local_typed_stored_attributes[attr_name][:types]
      type = Type::TypedStore.create_from_type(subtype)
      type.owner = owner
      defaultik.type = type
      subtypes.each do |name, (cast_type, options)|
        type.add_typed_key(name, cast_type, **options.symbolize_keys)
      end

      _local_typed_stored_attributes[attr_name][:decorated_type] = type

      type
    end

    attribute(attr_name, default: defaultik.proc)
  # Rails >=6.1, <=7.1
  else
    was_type = attributes_to_define_after_schema_loads[attr_name]&.first

    attribute(attr_name, default: defaultik.proc) do |subtype|
      subtypes = _local_typed_stored_attributes[attr_name][:types]
      subtype = _lookup_cast_type(attr_name, was_type, {}) if defined?(_lookup_cast_type)

      type = Type::TypedStore.create_from_type(subtype)
      type.owner = owner
      defaultik.type = type
      subtypes.each do |name, (cast_type, options)|
        type.add_typed_key(name, cast_type, **options.symbolize_keys)
      end

      # Make sure default attribute uses the correct type, so #changed? works as expected
      # This is dirty hack that makes Rails <7.2 works similar to Rails >=7.2. Please, upgrade :)
      if type.defaults.any? && _default_attributes[attr_name] && !_default_attributes[attr_name].type.is_a?(Type::TypedStore)
        _default_attributes[attr_name] =
          ActiveModel::Attribute.from_database(attr_name, _default_attributes[attr_name].value.deep_dup, type)
      end

      type
    end
  end
end

#_local_typed_stored_attributesObject



183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/store_attribute/active_record/store.rb', line 183

def _local_typed_stored_attributes
  return @local_typed_stored_attributes if _local_typed_stored_attributes?

  @local_typed_stored_attributes =
    if superclass.respond_to?(:_local_typed_stored_attributes)
      superclass._local_typed_stored_attributes.dup.tap do |h|
        h.transform_values! { |v| {owner: v[:owner], types: v[:types].dup} }
      end
    else
      Hash.new { |h, k| h[k] = {types: {}.with_indifferent_access} }.with_indifferent_access
    end
end

#_local_typed_stored_attributes?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/store_attribute/active_record/store.rb', line 179

def _local_typed_stored_attributes?
  instance_variable_defined?(:@local_typed_stored_attributes)
end

#_orig_store_accessor_without_typesObject



11
# File 'lib/store_attribute/active_record/store.rb', line 11

alias_method :_orig_store_accessor_without_types, :store_accessor

#_orig_store_without_typesObject



10
# File 'lib/store_attribute/active_record/store.rb', line 10

alias_method :_orig_store_without_types, :store

#_store_accessors_moduleObject

Rails 8.2 introduced named accessors module



278
279
280
281
282
283
284
285
286
# File 'lib/store_attribute/active_record/store.rb', line 278

def _store_accessors_module
  if const_defined?(:GeneratedStoreMethods, false)
    const_get(:GeneratedStoreMethods, false)
  else
    mod = const_set(:GeneratedStoreMethods, Module.new)
    include mod # rubocop:disable Layout/EmptyLinesAfterModuleInclusion
    mod
  end
end

#store(store_name, options = {}) ⇒ Object

Defines store on this model.

store_name The name of the store.

Options

The following options are accepted:

coder The coder of the store.

accessors An array of the accessors to the store.

Examples:

class User < ActiveRecord::Base
store :settings, accessors: [:color, :homepage, login_at: :datetime], coder: JSON
end


32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/store_attribute/active_record/store.rb', line 32

def store(store_name, options = {})
  accessors = options.delete(:accessors)
  accessor_related_options = options.slice(:prefix, :suffix)
  typed_accessors =
    if accessors && accessors.last.is_a?(Hash)
      accessors.pop
    else
      {}
    end

  _orig_store_without_types(store_name, options)
  store_accessor(store_name, *accessors, **accessor_related_options, **typed_accessors) if accessors
end

#store_accessor(store_name, *keys, prefix: nil, suffix: nil, **typed_keys) ⇒ Object

Adds additional accessors to an existing store on this model.

store_name The name of the store.

keys The array of the accessors to the store.

typed_keys The key-to-type hash of the accesors with type to the store.

prefix Accessor method name prefix

suffix Accessor method name suffix

Examples:

class SuperUser < User
store_accessor :settings, :privileges, login_at: :datetime
end


63
64
65
66
67
68
69
70
71
72
# File 'lib/store_attribute/active_record/store.rb', line 63

def store_accessor(store_name, *keys, prefix: nil, suffix: nil, **typed_keys)
  keys = keys.flatten
  typed_keys = typed_keys.except(keys)

  _orig_store_accessor_without_types(store_name, *(keys - typed_keys.keys), prefix: prefix, suffix: suffix)

  typed_keys.each do |key, type|
    store_attribute(store_name, key, type, prefix: prefix, suffix: suffix)
  end
end

#store_attribute(store_name, name, type = :value, prefix: nil, suffix: nil, **options) ⇒ Object

Adds additional accessors with a type to an existing store on this model. Type casting occurs every time you write data through accessor or update store itself and when object is loaded from database.

Note that if you update store explicitly then value isn't type casted.

store_name The name of the store.

name The name of the accessor to the store.

type A symbol such as :string or :integer, or a type object to be used for the accessor.

prefix Accessor method name prefix

suffix Accessor method name suffix

options A hash of cast type options such as precision, limit, scale.

Examples:

class MegaUser < User
store_attribute :settings, :ratio, :integer, limit: 1
store_attribute :settings, :login_at, :datetime

store_attribute :extra, :version, :integer, prefix: :meta
end

u = MegaUser.new(active: false, login_at: '2015-01-01 00:01', ratio: "63.4608", meta_version: "1")

u..is_a?(DateTime) # => true
u. = DateTime.new(2015,1,1,11,0,0)
u.ratio # => 63
u.meta_version #=> 1
u.reload

# After loading record from db store contains casted data
u.settings['login_at'] == DateTime.new(2015,1,1,11,0,0) # => true

# If you update store explicitly then the value returned
# by accessor isn't type casted
u.settings['ration'] = "3.141592653"
u.ratio # => "3.141592653"

# On the other hand, writing through accessor set correct data within store
u.ratio = "3.141592653"
u.ratio # => 3
u.settings['ratio'] # => 3

For more examples on using types, see documentation for ActiveRecord::Attributes.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/store_attribute/active_record/store.rb', line 124

def store_attribute(store_name, name, type = :value, prefix: nil, suffix: nil, **options)
  _orig_store_accessor_without_types(store_name, name.to_s, prefix: prefix, suffix: suffix)
  _define_predicate_method(name, prefix: prefix, suffix: suffix) if type == :boolean

  _define_store_attribute(store_name) if !_local_typed_stored_attributes? ||
    _local_typed_stored_attributes[store_name][:types].empty? ||
    # Defaults owner has changed, we must decorate the attribute to correctly propagate the defaults
    (
      options.key?(:default) && _local_typed_stored_attributes[store_name][:owner] != self
    )

  _local_typed_stored_attributes[store_name][:owner] = self if options.key?(:default) || !_local_typed_stored_attributes?
  _local_typed_stored_attributes[store_name][:types][name] = [type, options]

  # In case #decorate_attribute has already been invoked, add new type information right away
  if (dtype = _local_typed_stored_attributes[store_name][:decorated_type])
    dtype.add_typed_key(name, type, **options.symbolize_keys)
  end

  if store_attribute_register_attributes
    cast_type =
      if type == :value
        ActiveModel::Type::Value.new(**options.except(:default))
      elsif type.is_a?(Symbol)
        ActiveRecord::Type.lookup(type, **options.except(:default))
      else
        type
      end

    attribute(name, cast_type, **options)
  end
end