Module: Motor::Resources::FetchConfiguredModel

Defined in:
lib/motor/resources/fetch_configured_model.rb

Constant Summary collapse

CACHE_HASH =
HashWithIndifferentAccess.new
HAS_AND_BELONGS_TO_MANY_JOIN_MODEL_PREFIX =
'HABTM_'

Class Method Summary collapse

Class Method Details

.build_configured_model(model, config) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/motor/resources/fetch_configured_model.rb', line 26

def build_configured_model(model, config)
  klass = Class.new(model)
  klass.inheritance_column = nil if model.superclass.abstract_class

  define_class_name_method(klass, model)

  define_columns_hash(klass, config)
  define_default_scope(klass, config)
  define_column_reflections(klass, config)
  define_associations(klass, config)

  klass
end

.build_configured_model_from_configs(model, configs) ⇒ Object



204
205
206
207
208
209
210
211
212
# File 'lib/motor/resources/fetch_configured_model.rb', line 204

def build_configured_model_from_configs(model, configs)
  resource_config = configs.find { |r| r.name == model.name.underscore }

  if resource_config
    build_configured_model(model, resource_config.preferences)
  else
    define_class_name_method(Class.new(model), model)
  end
end

.call(model, cache_key:) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/motor/resources/fetch_configured_model.rb', line 12

def call(model, cache_key:)
  configs = Motor::Configs::LoadFromCache.load_resources(cache_key: cache_key)

  return model if model.name == 'ActiveStorage::Attachment'
  return model if configs.blank? || sti_model?(model)

  maybe_fetch_from_cache(
    model,
    cache_key.to_s + model.object_id.to_s,
    -> { build_configured_model_from_configs(model, configs) },
    ->(klass) { configure_reflection_classes(klass, cache_key) }
  )
end

.configure_reflection_class?(ref) ⇒ Boolean

Returns:

  • (Boolean)


214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/motor/resources/fetch_configured_model.rb', line 214

def configure_reflection_class?(ref)
  begin
    return false unless ref.klass
  rescue StandardError
    return false
  end

  return false if ref.klass.anonymous?
  return false if ref.klass.name.demodulize.starts_with?(HAS_AND_BELONGS_TO_MANY_JOIN_MODEL_PREFIX)

  true
end

.configure_reflection_classes(klass, cache_key) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/motor/resources/fetch_configured_model.rb', line 139

def configure_reflection_classes(klass, cache_key)
  klass._reflections.each do |key, ref|
    next unless configure_reflection_class?(ref)

    ref_dup = ref.dup

    if ref.klass.name == klass.name
      ref_dup.instance_variable_set(:@klass, klass)
    else
      ref_dup.instance_variable_set(:@klass, call(ref.klass, cache_key: cache_key))
    end

    klass.reflections[key] = ref_dup
  end

  klass._reflections = klass.reflections

  klass
end

.define_association(klass, name, options, filters) ⇒ Object



171
172
173
174
175
176
177
178
179
# File 'lib/motor/resources/fetch_configured_model.rb', line 171

def define_association(klass, name, options, filters)
  if options[:class_name] == 'ActiveStorage::Attachment'
    klass.has_many_attached name.delete_suffix('_attachments').to_sym
  elsif filters.present?
    klass.has_many(name.to_sym, -> { filter(filters).tap(&:arel) }, **options.symbolize_keys)
  else
    klass.has_many(name.to_sym, **options.symbolize_keys)
  end
end

.define_associations(klass, config) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/motor/resources/fetch_configured_model.rb', line 159

def define_associations(klass, config)
  config.fetch(:associations, []).each do |association|
    next unless association[:virtual]

    options = normalize_association_params(association)

    filters = options.delete(:filters)

    define_association(klass, association[:name], options, filters)
  end
end

.define_belongs_to_reflection(klass, config) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/motor/resources/fetch_configured_model.rb', line 114

def define_belongs_to_reflection(klass, config)
  klass.belongs_to(config[:name].to_sym,
                   class_name: config[:model_name]&.classify,
                   foreign_key: config[:foreign_key],
                   polymorphic: config[:polymorphic],
                   primary_key: config[:primary_key],
                   optional: true)
end

.define_class_name_method(klass, model) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/motor/resources/fetch_configured_model.rb', line 54

def define_class_name_method(klass, model)
  klass.instance_variable_set(:@__motor_model_name, model.name)

  klass.instance_eval do
    def name
      @__motor_model_name
    end

    def inspect
      super.gsub(/\#<Class:0x\w+>/, name)
    end

    def to_s
      super.gsub(/\#<Class:0x\w+>/, name)
    end

    def anonymous?
      true
    end
  end

  klass
end

.define_column_reflections(klass, config) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/motor/resources/fetch_configured_model.rb', line 99

def define_column_reflections(klass, config)
  config.fetch(:columns, []).each do |column|
    reference = column[:reference]

    next if reference.blank?
    next unless reference[:virtual]

    if reference[:reference_type] == 'belongs_to'
      define_belongs_to_reflection(klass, reference)
    else
      define_has_one_reflection(klass, reference)
    end
  end
end

.define_columns_hash(klass, config) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/motor/resources/fetch_configured_model.rb', line 78

def define_columns_hash(klass, config)
  return klass if config[:custom_sql].blank?

  columns = Resources::CustomSqlColumnsCache.call(config[:custom_sql])

  columns_hash =
    columns.each_with_object({}) do |column, acc|
      acc[column[:name]] = ActiveRecord::ConnectionAdapters::Column.new(column[:name], nil)
    end

  klass.instance_variable_set(:@__motor_custom_sql_columns_hash, columns_hash)

  # rubocop:disable Naming/MemoizedInstanceVariableName
  klass.instance_eval do
    def columns_hash
      @__motor__columns_hash ||= @__motor_custom_sql_columns_hash.merge(super)
    end
  end
  # rubocop:enable Naming/MemoizedInstanceVariableName
end

.define_default_scope(klass, config) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/motor/resources/fetch_configured_model.rb', line 40

def define_default_scope(klass, config)
  return klass if config[:custom_sql].blank?

  klass.instance_variable_set(:@__motor_custom_sql, config[:custom_sql].squish)

  klass.instance_eval do
    default_scope do
      from(Arel.sql("(#{self.klass.instance_variable_get(:@__motor_custom_sql)})").as(table_name))
    end
  end

  klass
end

.define_has_one_reflection(klass, config) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/motor/resources/fetch_configured_model.rb', line 123

def define_has_one_reflection(klass, config)
  options = {
    class_name: config[:model_name].classify,
    foreign_key: config[:foreign_key],
    primary_key: config[:primary_key]
  }

  options = options.merge(config[:options] || {})

  if config[:model_name] == 'active_storage/attachment'
    klass.has_one_attached config[:name].delete_suffix('_attachment').to_sym
  else
    klass.has_one(config[:name].to_sym, **options.symbolize_keys)
  end
end

.maybe_fetch_from_cache(model, cache_key, miss_cache_block, postprocess_block) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/motor/resources/fetch_configured_model.rb', line 190

def maybe_fetch_from_cache(model, cache_key, miss_cache_block, postprocess_block)
  return miss_cache_block.call unless cache_key

  if CACHE_HASH[model.name] && CACHE_HASH[model.name][:key] == cache_key
    CACHE_HASH[model.name][:value]
  else
    result = miss_cache_block.call

    CACHE_HASH[model.name] = { key: cache_key, value: result }

    postprocess_block.call(result)
  end
end

.normalize_association_params(params) ⇒ Object



181
182
183
184
185
186
187
188
# File 'lib/motor/resources/fetch_configured_model.rb', line 181

def normalize_association_params(params)
  options = params.slice(:foreign_key, :primary_key).merge(dependent: :destroy)

  options[:class_name] = params[:model_name].classify
  options[:as] = params[:foreign_key].delete_suffix('_id') if params[:polymorphic]

  options.merge(params[:options] || {})
end

.sti_model?(model) ⇒ Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/motor/resources/fetch_configured_model.rb', line 227

def sti_model?(model)
  !model.superclass.abstract_class && model.columns_hash[model.inheritance_column.to_s]
end