Module: Motor::Resources::FetchConfiguredModel

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

Constant Summary collapse

CACHE_HASH =
HashWithIndifferentAccess.new

Class Method Summary collapse

Class Method Details

.build_configured_model(model, config) ⇒ Object



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

def build_configured_model(model, config)
  klass = Class.new(model)

  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

.call(model, cache_key:) ⇒ Object



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

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

  maybe_fetch_from_cache(
    model,
    cache_key,
    lambda {
      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
    },
    ->(klass) { configure_reflection_classes(klass, cache_key) }
  )
end

.configure_reflection_classes(klass, cache_key) ⇒ Object



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

def configure_reflection_classes(klass, cache_key)
  klass.reflections.each do |key, ref|
    begin
      next unless ref.klass
      next if ref.klass.anonymous?
    rescue StandardError
      next
    end

    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_associations(klass, config) ⇒ Object



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

def define_associations(klass, config)
  config.fetch(:associations, []).each do |association|
    is_virtual, is_polymorphic = association.values_at(:virtual, :polymorphic)

    next unless is_virtual

    options = association.slice(:foreign_key, :primary_key)
    options[:class_name] = association[:model_name].classify
    options[:as] = association[:foreign_key].delete_suffix('_id') if is_polymorphic

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

    klass.has_many(association[:name].to_sym, **options.symbolize_keys)
  end
end

.define_belongs_to_reflection(klass, config) ⇒ Object



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

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



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

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



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

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



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

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



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

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



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

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] || {})

  klass.has_one(config[:name].to_sym, **options.symbolize_keys)
end

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



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

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