brick 1.0.4 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe6bf3e38aef34261d9398b25f804504e7152721673b0534253c4284c9018013
4
- data.tar.gz: 766af0df2edc3dc8c8c73a5d1ecd99c8d738ed7cd00fc21c79c38ac3db991819
3
+ metadata.gz: b65cbc8bbf472c887136395afb13f9dcf6e460f0c279dda0372939da8863178f
4
+ data.tar.gz: d63ae1e8795bb788e67c05d98f245c086aa773fbfc5585f716e3371652976878
5
5
  SHA512:
6
- metadata.gz: 40f2eacaf7fada3056f5bfc701b01441ad02e34e76e87dc8e53b36a546c87216e8a319c3f0763b8fecff49344224d5660ff5b875937a3e30e2e7c80c662f684c
7
- data.tar.gz: 7901020af2ec1759c27c16a0c3a3ffbcddcd09691c652c14c02781823964e36409533afc562fb0d9beb508cc3329b601d6fe8c266c101f6db6d93aad5e055a3e
6
+ metadata.gz: e27c5bc4385ae585e1904fc142aa6e0a61b1c780c6e9d68efc4b6ce1b60f985db71922466ad5dff365e3c239a0220411af90ec2f148705bdc5a993e552494c50
7
+ data.tar.gz: 2937ec1ecb167d7db208c0f46aaab60670b95403ba22b464597c254c782e9075e0245514fd950fbb4267c3e3f206b7b49969cd5052dd3d6d89db41a7883cd7d9
data/lib/brick/config.rb CHANGED
@@ -65,6 +65,15 @@ module Brick
65
65
  @mutex.synchronize { @additional_references = references }
66
66
  end
67
67
 
68
+ # Associations to treat as a has_one
69
+ def has_ones
70
+ @mutex.synchronize { @has_ones }
71
+ end
72
+
73
+ def has_ones=(references)
74
+ @mutex.synchronize { @has_ones = references }
75
+ end
76
+
68
77
  def skip_database_views
69
78
  @mutex.synchronize { @skip_database_views }
70
79
  end
@@ -61,14 +61,15 @@ module ActiveRecord
61
61
  wheres = {}
62
62
  rel_joins = []
63
63
  params.each do |k, v|
64
- if (ks = k.split('.')).length > 1
64
+ case (ks = k.split('.')).length
65
+ when 1
66
+ next unless klass._brick_get_fks.include?(k)
67
+ when 2
65
68
  assoc_name = ks.first.to_sym
66
69
  # Make sure it's a good association name and that the model has that column name
67
70
  next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.map(&:name)&.include?(ks.last)
68
71
 
69
72
  rel_joins << assoc_name unless rel_joins.include?(assoc_name)
70
- else
71
- next unless klass._brick_get_fks.include?(k)
72
73
  end
73
74
  wheres[k] = v.split(',')
74
75
  end
@@ -79,6 +80,18 @@ module ActiveRecord
79
80
  end
80
81
  end
81
82
  end
83
+
84
+ module Inheritance
85
+ module ClassMethods
86
+ private
87
+
88
+ alias _brick_find_sti_class find_sti_class
89
+ def find_sti_class(type_name)
90
+ ::Brick.sti_models[type_name] = { base: self } unless type_name.blank?
91
+ _brick_find_sti_class(type_name)
92
+ end
93
+ end
94
+ end
82
95
  end
83
96
 
84
97
  # Object.class_exec do
@@ -112,6 +125,11 @@ class Object
112
125
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
113
126
  table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
114
127
 
128
+ # Adjust for STI if we know of a base model for the requested model name
129
+ if (base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil))
130
+ table_name = base_model.table_name
131
+ end
132
+
115
133
  # Maybe, just maybe there's a database table that will satisfy this need
116
134
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
117
135
  build_model(model_name, singular_table_name, table_name, relations, matching)
@@ -137,8 +155,9 @@ class Object
137
155
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
138
156
  raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
139
157
  end
140
- code = +"class #{model_name} < ActiveRecord::Base\n"
141
- built_model = Class.new(ActiveRecord::Base) do |new_model_class|
158
+ base_model = ::Brick.sti_models[model_name]&.fetch(:base, nil) || ActiveRecord::Base
159
+ code = +"class #{model_name} < #{base_model.name}\n"
160
+ built_model = Class.new(base_model) do |new_model_class|
142
161
  Object.const_set(model_name.to_sym, new_model_class)
143
162
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
144
163
  code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
@@ -175,8 +194,10 @@ class Object
175
194
  end
176
195
 
177
196
  fks = relation[:fks] || {}
178
- fks.each do |_constraint_name, assoc|
179
- assoc_name = assoc[:assoc_name]
197
+ # Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
198
+ hmts = fks.each_with_object(Hash.new { |h, k| h[k] = [] }) do |fk, hmts|
199
+ # The key in each hash entry (fk.first) is the constraint name
200
+ assoc_name = (assoc = fk.last)[:assoc_name]
180
201
  inverse_assoc_name = assoc[:inverse][:assoc_name]
181
202
  options = {}
182
203
  singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
@@ -184,6 +205,14 @@ class Object
184
205
  need_class_name = singular_table_name.underscore != assoc_name
185
206
  need_fk = "#{assoc_name}_id" != assoc[:fk]
186
207
  inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
208
+ if (has_ones = ::Brick.config.has_ones&.fetch(assoc[:inverse][:alternate_name].camelize, nil))&.key?(singular_inv_assoc_name = ActiveSupport::Inflector.singularize(inverse_assoc_name))
209
+ inverse_assoc_name = if has_ones[singular_inv_assoc_name]
210
+ need_inverse_of = true
211
+ has_ones[singular_inv_assoc_name]
212
+ else
213
+ singular_inv_assoc_name
214
+ end
215
+ end
187
216
  :belongs_to
188
217
  else
189
218
  # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
@@ -191,29 +220,66 @@ class Object
191
220
  assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
192
221
  need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
193
222
  # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
194
- :has_many
223
+ if (has_ones = ::Brick.config.has_ones&.fetch(model_name, nil))&.key?(singular_assoc_name = ActiveSupport::Inflector.singularize(assoc_name))
224
+ assoc_name = if has_ones[singular_assoc_name]
225
+ need_class_name = true
226
+ has_ones[singular_assoc_name]
227
+ else
228
+ singular_assoc_name
229
+ end
230
+ :has_one
231
+ else
232
+ :has_many
233
+ end
195
234
  end
235
+ # Figure out if we need to specially call out the class_name and/or foreign key
236
+ # (and if either of those then definitely also a specific inverse_of)
196
237
  options[:class_name] = singular_table_name.camelize if need_class_name
197
- # Figure out if we need to specially call out the foreign key
238
+ # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
198
239
  if need_fk # Funky foreign key?
199
- options[:foreign_key] = assoc[:fk].to_sym
240
+ options[:foreign_key] = if assoc[:fk].is_a?(Array)
241
+ assoc_fk = assoc[:fk].uniq
242
+ assoc_fk.length < 2 ? assoc_fk.first : assoc_fk
243
+ else
244
+ assoc[:fk].to_sym
245
+ end
200
246
  end
201
- options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk
202
- assoc_name = assoc_name.to_sym
203
- code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
204
- self.send(macro, assoc_name, **options)
247
+ options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
205
248
 
206
- # Look for any valid "has_many :through"
249
+ # Prepare a list of entries for "has_many :through"
207
250
  if macro == :has_many
208
251
  relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
209
252
  next if k == assoc[:fk]
210
253
 
211
- hmt_fk = ActiveSupport::Inflector.pluralize(hmt_fk)
212
- code << " has_many :#{hmt_fk}, through: #{assoc_name.inspect}\n"
213
- self.send(:has_many, hmt_fk.to_sym, **{ through: assoc_name })
254
+ hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
214
255
  end
215
256
  end
257
+
258
+ # And finally create a has_one, has_many, or belongs_to for this association
259
+ assoc_name = assoc_name.to_sym
260
+ code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
261
+ self.send(macro, assoc_name, **options)
262
+ hmts
216
263
  end
264
+ hmts.each do |hmt_fk, fks|
265
+ fks.each do |fk|
266
+ source = nil
267
+ this_hmt_fk = if fks.length > 1
268
+ singular_assoc_name = ActiveSupport::Inflector.singularize(fk.first[:inverse][:assoc_name])
269
+ source = fk.last
270
+ through = ActiveSupport::Inflector.pluralize(fk.first[:alternate_name])
271
+ "#{singular_assoc_name}_#{hmt_fk}"
272
+ else
273
+ through = fk.first[:assoc_name]
274
+ hmt_fk
275
+ end
276
+ code << " has_many :#{this_hmt_fk}, through: #{(assoc_name = through.to_sym).to_sym.inspect}#{", source: :#{source}" if source}\n"
277
+ options = { through: assoc_name }
278
+ options[:source] = source.to_sym if source
279
+ self.send(:has_many, this_hmt_fk.to_sym, **options)
280
+ end
281
+ end
282
+
217
283
  code << "end # model #{model_name}\n\n"
218
284
  end # class definition
219
285
  [built_model, code]
@@ -257,7 +323,7 @@ class Object
257
323
  end
258
324
 
259
325
  def _brick_get_hm_assoc_name(relation, hm_assoc)
260
- if relation[:hm_counts][hm_assoc[:assoc_name]] > 1
326
+ if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
261
327
  [ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name]), true]
262
328
  else
263
329
  [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
@@ -273,9 +339,13 @@ end
273
339
  module ActiveRecord::ConnectionHandling
274
340
  alias _brick_establish_connection establish_connection
275
341
  def establish_connection(*args)
276
- x = _brick_establish_connection(*args)
342
+ conn = _brick_establish_connection(*args)
343
+ _brick_reflect_tables
344
+ conn
345
+ end
277
346
 
278
- if (relations = ::Brick.relations).empty?
347
+ def _brick_reflect_tables
348
+ if (relations = ::Brick.relations).empty?
279
349
  # Only for Postgres? (Doesn't work in sqlite3)
280
350
  # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
281
351
 
@@ -367,7 +437,7 @@ module ActiveRecord::ConnectionHandling
367
437
  case ActiveRecord::Base.connection.adapter_name
368
438
  when 'PostgreSQL', 'Mysql2'
369
439
  sql = ActiveRecord::Base.send(:sanitize_sql_array, [
370
- "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME, kcu1.CONSTRAINT_NAME
440
+ "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME AS primary_table, kcu1.CONSTRAINT_NAME
371
441
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
372
442
  INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
373
443
  ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
@@ -389,9 +459,10 @@ module ActiveRecord::ConnectionHandling
389
459
  else
390
460
  end
391
461
  if sql
392
- result = ActiveRecord::Base.connection.execute(sql)
393
- result = result.values unless result.is_a?(Array)
394
- result.each { |fk| ::Brick._add_bt_and_hm(fk, relations) }
462
+ ActiveRecord::Base.connection.execute(sql).each do |fk|
463
+ fk = fk.values unless fk.is_a?(Array)
464
+ ::Brick._add_bt_and_hm(fk, relations)
465
+ end
395
466
  end
396
467
  end
397
468
 
@@ -403,7 +474,6 @@ module ActiveRecord::ConnectionHandling
403
474
 
404
475
  # relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
405
476
  # Layout table describes permissioned hierarchy throughout
406
- x
407
477
  end
408
478
  end
409
479
 
@@ -5,11 +5,8 @@ module Brick
5
5
  # See http://guides.rubyonrails.org/engines.html
6
6
  class Engine < ::Rails::Engine
7
7
  # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
8
- puts "BEFORE - engine set config"
9
8
  config.brick = ActiveSupport::OrderedOptions.new
10
- # initializer 'brick.initialisation' do |app|
11
9
  ActiveSupport.on_load(:before_initialize) do |app|
12
- puts "BEFORE - engine initialisation"
13
10
  ::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
14
11
  ::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
15
12
  ::Brick.enable_views = app.config.brick.fetch(:enable_views, true)
@@ -25,83 +22,72 @@ module Brick
25
22
  # Additional references (virtual foreign keys)
26
23
  ::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
27
24
 
28
- # After we're initialized and before running the rest of stuff, put our configuration in place
29
- ActiveSupport.on_load(:after_initialize) do |xyz|
30
- puts "AFTER - engine initialisation"
31
- # ====================================
32
- # Dynamically create generic templates
33
- # ====================================
34
- if ::Brick.enable_views?
35
- ActionView::LookupContext.class_exec do
36
- alias :_brick_template_exists? :template_exists?
37
- def template_exists?(*args, **options)
38
- unless (is_template_exists = _brick_template_exists?(*args, **options))
39
- # Need to return true if we can fill in the blanks for a missing one
40
- # args will be something like: ["index", ["categories"]]
41
- model = args[1].map(&:camelize).join('::').singularize.constantize
42
- if (
43
- is_template_exists = model && (
44
- ['index', 'show'].include?(args.first) || # Everything has index and show
45
- # Only CRU stuff has create / update / destroy
46
- (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
47
- )
48
- )
49
- instance_variable_set(:@_brick_model, model)
50
- end
25
+ # Has one relationships
26
+ ::Brick.has_ones = app.config.brick.fetch(:has_ones, nil)
27
+ end
28
+
29
+ # After we're initialized and before running the rest of stuff, put our configuration in place
30
+ ActiveSupport.on_load(:after_initialize) do
31
+ # ====================================
32
+ # Dynamically create generic templates
33
+ # ====================================
34
+ if ::Brick.enable_views?
35
+ ActionView::LookupContext.class_exec do
36
+ alias :_brick_template_exists? :template_exists?
37
+ def template_exists?(*args, **options)
38
+ unless (is_template_exists = _brick_template_exists?(*args, **options))
39
+ # Need to return true if we can fill in the blanks for a missing one
40
+ # args will be something like: ["index", ["categories"]]
41
+ model = args[1].map(&:camelize).join('::').singularize.constantize
42
+ if (
43
+ is_template_exists = model && (
44
+ ['index', 'show'].include?(args.first) || # Everything has index and show
45
+ # Only CRU stuff has create / update / destroy
46
+ (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
47
+ )
48
+ )
49
+ instance_variable_set(:@_brick_model, model)
51
50
  end
52
- is_template_exists
53
51
  end
52
+ is_template_exists
53
+ end
54
54
 
55
- alias :_brick_find_template :find_template
56
- def find_template(*args, **options)
57
- if @_brick_model
58
- model_name = @_brick_model.name
59
- pk = @_brick_model.primary_key
60
- obj_name = model_name.underscore
61
- table_name = model_name.pluralize.underscore
62
- # This gets has_many as well as has_many :through
63
- # %%% weed out ones that don't have an available model to reference
64
- bts, hms = @_brick_model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
65
- case a.macro
66
- when :belongs_to
67
- # Build #brick_descrip if needed
68
- unless a.klass.instance_methods(false).include?(:brick_descrip)
69
- descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
70
- (::Brick.config.metadata_columns || []) -
71
- [a.klass.primary_key]).first&.to_sym
72
- if descrip_col
73
- a.klass.define_method :brick_descrip do
74
- send(descrip_col)
75
- end
76
- end
77
- end
78
-
79
- s.first[a.foreign_key] = [a.name, a.klass]
80
- when :has_many
81
- s.last[a.name] = a
82
- end
83
- s
84
- end
85
- # Weed out has_manys that go to an associative table
86
- associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
87
- s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
88
- end
89
- hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>HM#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
90
- hms_columns = hms.each_with_object(+'') do |hm, s|
91
- hm_fk_name = if hm.last.options[:through]
92
- associative = associatives[hm.last.name]
93
- "'#{associative.name}.#{associative.foreign_key}'"
94
- else
95
- hm.last.foreign_key
96
- end
97
- s << "<td>
98
- <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) %>
55
+ alias :_brick_find_template :find_template
56
+ def find_template(*args, **options)
57
+ if @_brick_model
58
+ model_name = @_brick_model.name
59
+ pk = @_brick_model.primary_key
60
+ obj_name = model_name.underscore
61
+ table_name = model_name.pluralize.underscore
62
+ # This gets has_many as well as has_many :through
63
+ # %%% weed out ones that don't have an available model to reference
64
+ bts, hms = ::Brick.get_bts_and_hms(@_brick_model)
65
+ # Weed out has_manys that go to an associative table
66
+ associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
67
+ s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
68
+ end
69
+ hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>H#{hm.last.macro == :has_one ? 'O' : 'M'}#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
70
+ hms_columns = hms.each_with_object(+'') do |hm, s|
71
+ hm_fk_name = if hm.last.options[:through]
72
+ associative = associatives[hm.last.name]
73
+ "'#{associative.name}.#{associative.foreign_key}'"
74
+ else
75
+ hm.last.foreign_key
76
+ end
77
+ s << if hm.last.macro == :has_many
78
+ "<td>
79
+ #\{#{obj_name}.#{hm.first}.count\} #{hm.first}<%= link_to \"\", #{hm.last&.klass&.name&.underscore&.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
99
80
  </td>\n"
100
- end
81
+ else # has_one
82
+ "<td>
83
+ <%= obj = #{obj_name}.#{hm.first}; link_to(obj.brick_descrip, obj) if obj %>
84
+ </td>\n"
85
+ end
86
+ end
101
87
 
102
- inline = case args.first
103
- when 'index'
104
- "<p style=\"color: green\"><%= notice %></p>
88
+ inline = case args.first
89
+ when 'index'
90
+ "<p style=\"color: green\"><%= notice %></p>
105
91
 
106
92
  <h1>#{model_name.pluralize}</h1>
107
93
  <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
@@ -109,7 +95,7 @@ module Brick
109
95
  <table id=\"#{table_name}\">
110
96
  <tr>
111
97
  <% is_first = true; is_need_id_col = nil
112
- bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last.last.name}, #{v.last.last.primary_key.inspect}]"}.join(', ')} }
98
+ bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last[1].name}, #{v.last[1].primary_key.inspect}]"}.join(', ')} }
113
99
  @#{table_name}.columns.map(&:name).each do |col| %>
114
100
  <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
115
101
  <th>
@@ -145,9 +131,9 @@ module Brick
145
131
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
146
132
  <td>
147
133
  <% if (bt = bts[k]) %>
148
- <%= obj = bt[1].find_by(bt.last => val); link_to obj.brick_descrip, obj %>
134
+ <%= obj = bt[1].find_by(bt.last => val); link_to(obj.brick_descrip, obj) if obj %>
149
135
  <% elsif is_first %>
150
- <%= is_first = false; link_to val, #{obj_name} %>
136
+ <%= is_first = false; link_to val, #{obj_name}_path(#{obj_name}.#{pk}) %>
151
137
  <% else %>
152
138
  <%= val %>
153
139
  <% end %>
@@ -161,63 +147,58 @@ module Brick
161
147
 
162
148
  #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
163
149
  "
164
- when 'show'
165
- "<%= @#{@_brick_model.name.underscore}.inspect %>"
166
- end
167
- # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
168
- keys = options.has_key?(:locals) ? options[:locals].keys : []
169
- handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
170
- ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
171
- else
172
- _brick_find_template(*args, **options)
150
+ when 'show'
151
+ "<%= @#{@_brick_model.name.underscore}.inspect %>"
173
152
  end
153
+ # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
154
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
155
+ handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
156
+ ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
157
+ else
158
+ _brick_find_template(*args, **options)
174
159
  end
175
160
  end
176
161
  end
162
+ end
177
163
 
178
- if ::Brick.enable_routes?
179
- ActionDispatch::Routing::RouteSet.class_exec do
180
- alias _brick_finalize_routeset! finalize!
181
- def finalize!(*args, **options)
182
- unless @finalized
183
- existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
184
- ::Rails.application.routes.append do
185
- # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
186
- # If auto-controllers and auto-models are both enabled then this makes sense:
187
- relations = (::Brick.instance_variable_get(:@relations) || {})[ActiveRecord::Base.connection_pool.object_id] || {}
188
- relations.each do |k, v|
189
- unless existing_controllers.key?(controller_name = k.underscore.pluralize)
190
- options = {}
191
- options[:only] = [:index, :show] if v.key?(:isView)
192
- send(:resources, controller_name.to_sym, **options)
193
- end
164
+ if ::Brick.enable_routes?
165
+ ActionDispatch::Routing::RouteSet.class_exec do
166
+ alias _brick_finalize_routeset! finalize!
167
+ def finalize!(*args, **options)
168
+ unless @finalized
169
+ existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
170
+ ::Rails.application.routes.append do
171
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
172
+ # If auto-controllers and auto-models are both enabled then this makes sense:
173
+ ::Brick.relations.each do |k, v|
174
+ unless existing_controllers.key?(controller_name = k.underscore.pluralize)
175
+ options = {}
176
+ options[:only] = [:index, :show] if v.key?(:isView)
177
+ send(:resources, controller_name.to_sym, **options)
194
178
  end
195
179
  end
196
180
  end
197
- _brick_finalize_routeset!(*args, **options)
198
181
  end
182
+ _brick_finalize_routeset!(*args, **options)
199
183
  end
200
184
  end
185
+ end
201
186
 
202
- # Additional references (virtual foreign keys)
203
- if (ars = ::Brick.config.additional_references)
204
- ars = ars.call if ars.is_a?(Proc)
205
- ars = ars.to_a unless ars.is_a?(Array)
206
- ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
207
- ars.each do |fk|
208
- ::Brick._add_bt_and_hm(fk[0..2])
209
- end
187
+ # Additional references (virtual foreign keys)
188
+ if (ars = ::Brick.config.additional_references)
189
+ ars.each do |fk|
190
+ ::Brick._add_bt_and_hm(fk[0..2])
210
191
  end
192
+ end
211
193
 
212
- # Find associative tables that can be set up for has_many :through
213
- ::Brick.relations.each do |_key, tbl|
214
- tbl_cols = tbl[:cols].keys
215
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
216
- # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
217
- # foreign keys then it can act as an associative table and thus be used with has_many :through.
218
- if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - tbl[:pkey].values.first).length.zero?
219
- fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
220
- end
194
+ # Find associative tables that can be set up for has_many :through
195
+ ::Brick.relations.each do |_key, tbl|
196
+ tbl_cols = tbl[:cols].keys
197
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = [fk.last[:assoc_name], fk.last[:inverse_table]] if fk.last[:is_bt]; s }
198
+ # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
199
+ # foreign keys then it can act as an associative table and thus be used with has_many :through.
200
+ if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
201
+ fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
221
202
  end
222
203
  end
223
204
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 4
8
+ TINY = 7
9
9
 
10
10
  # PRE is nil unless it's a pre-release (beta, RC, etc.)
11
11
  PRE = nil
data/lib/brick.rb CHANGED
@@ -81,6 +81,10 @@ if Gem::Specification.all_names.any? { |g| g.start_with?('rails-') }
81
81
  require 'brick/frameworks/rails'
82
82
  end
83
83
  module Brick
84
+ def self.sti_models
85
+ @sti_models ||= {}
86
+ end
87
+
84
88
  class << self
85
89
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
86
90
  def relations
@@ -90,6 +94,30 @@ module Brick
90
94
  (connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
91
95
  end
92
96
 
97
+ def get_bts_and_hms(model)
98
+ model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
99
+ case a.macro
100
+ when :belongs_to
101
+ # Build #brick_descrip if needed
102
+ if a.klass.instance_methods(false).exclude?(:brick_descrip)
103
+ descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
104
+ (::Brick.config.metadata_columns || []) -
105
+ [a.klass.primary_key]).first&.to_sym
106
+ if descrip_col
107
+ a.klass.define_method :brick_descrip do
108
+ send(descrip_col)
109
+ end
110
+ end
111
+ end
112
+
113
+ s.first[a.foreign_key] = [a.name, a.klass]
114
+ when :has_many, :has_one
115
+ s.last[a.name] = a
116
+ end
117
+ s
118
+ end
119
+ end
120
+
93
121
  # Switches Brick auto-models on or off, for all threads
94
122
  # @api public
95
123
  def enable_models=(value)
@@ -159,8 +187,28 @@ module Brick
159
187
 
160
188
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
161
189
  # @api public
162
- def additional_references=(value)
163
- Brick.config.additional_references = value
190
+ def additional_references=(ars)
191
+ if ars
192
+ ars = ars.call if ars.is_a?(Proc)
193
+ ars = ars.to_a unless ars.is_a?(Array)
194
+ ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
195
+ Brick.config.additional_references = ars
196
+ end
197
+ end
198
+
199
+ # Associations to treat as a has_one
200
+ # @api public
201
+ def has_ones=(hos)
202
+ if hos
203
+ hos = hos.call if hos.is_a?(Proc)
204
+ hos = hos.to_a unless hos.is_a?(Array)
205
+ hos = [hos] unless hos.empty? || hos.first.is_a?(Array)
206
+ # Translate to being nested hashes
207
+ Brick.config.has_ones = hos&.each_with_object(Hash.new { |h, k| h[k] = {} }) do |v, s|
208
+ s[v.first][v[1]] = v[2] if v[1]
209
+ s
210
+ end
211
+ end
164
212
  end
165
213
 
166
214
 
@@ -48,6 +48,14 @@ module Brick
48
48
  # Brick.additional_references = [['orders', 'customer_id', 'customer'],
49
49
  # ['customer', 'region_id', 'regions']]
50
50
 
51
+ # # By default primary tables involved in a foreign key relationship will indicate a \"has_many\" relationship pointing
52
+ # # back to the foreign table. In order to represent a \"has_one\" association instead, an override can be provided
53
+ # # using the primary model name and the association name which you instead want to have treated as a \"has_one\":
54
+ # Brick.has_ones = [['User', 'user_profile']]
55
+ # # If you want to use an alternate name for the \"has_one\", such as in the case above calling the association \"profile\"
56
+ # # instead of \"user_profile\", then apply that as a third parameter like this:
57
+ # Brick.has_ones = [['User', 'user_profile', 'profile']]
58
+
51
59
  # # We normally don't consider the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\" to count when
52
60
  # # finding tables which can serve as associative tables in an N:M association. That is, ones that can be a
53
61
  # # part of a has_many :through association. If you want to use different exclusion columns than our defaults
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brick
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorin Thwaits
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-16 00:00:00.000000000 Z
11
+ date: 2022-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord