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
39
# 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)
  define_searchable_columns_method(klass, config)

  klass
end

.build_configured_model_from_configs(model, configs) ⇒ Object



223
224
225
226
227
228
229
230
231
# File 'lib/motor/resources/fetch_configured_model.rb', line 223

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)


233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/motor/resources/fetch_configured_model.rb', line 233

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



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/motor/resources/fetch_configured_model.rb', line 158

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



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

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



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

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



133
134
135
136
137
138
139
140
# File 'lib/motor/resources/fetch_configured_model.rb', line 133

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



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

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



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/motor/resources/fetch_configured_model.rb', line 118

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



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
# File 'lib/motor/resources/fetch_configured_model.rb', line 91

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,
          ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: column[:column_type],
                                                                type: column[:column_type].to_sym)
        )
    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



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

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

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

  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



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

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

.define_searchable_columns_method(klass, config) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/motor/resources/fetch_configured_model.rb', line 79

def define_searchable_columns_method(klass, config)
  return if config[:searchable_columns].blank?

  klass.instance_variable_set(:@__motor_searchable_columns, config[:searchable_columns])

  klass.instance_eval do
    def motor_searchable_columns
      @__motor_searchable_columns
    end
  end
end

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



209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/motor/resources/fetch_configured_model.rb', line 209

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



200
201
202
203
204
205
206
207
# File 'lib/motor/resources/fetch_configured_model.rb', line 200

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)


246
247
248
# File 'lib/motor/resources/fetch_configured_model.rb', line 246

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