brick 1.0.5 → 1.0.8

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: 9cd49783dae6701760920fe007cbde7382fd3dbaf5e8f7472d475c0fe8aaf660
4
- data.tar.gz: d2330f28e87dde00b1377f6cbca31cbbfe2dd3b2c495f1fbd0aebc0f8a951504
3
+ metadata.gz: 7e06e6aaa9b7fef7730d56404912ab186a29f6e698f6dbb0d9c551cf2008c0b1
4
+ data.tar.gz: 7e546071063729d5b85b2f146bb401c7cbfc462f6dea0a701ed8c839cb883de5
5
5
  SHA512:
6
- metadata.gz: 2a3c7ba47d32f3a6ab6c9be838006826f5266efb1349d90ce7d53b5e8f89ea078bdcb699a7db8084176e2de444a27931ce8e0216af666c25704c0568b59ac827
7
- data.tar.gz: 41fccf3387315cd54fd596955c3f07760941cd4aa51959dc99f87d0b6fb38de742ad11fe7dabb084a3e72167c340b8aafce838c7704626359ccb41878e202ae2
6
+ metadata.gz: 83499e7959f98bb323f44593f6472ba1996815ed5f286a119f5adfbd2b690be3f9cd3556166cff3efda250fd609b6d06f4054aea599b0e9587191b4cb1d7a3cb
7
+ data.tar.gz: 6ded71697d4cce4d931afb54869639d6c2d0b32462806c0c0c7826ceb46fd5f7d7c2476f29e5c9302c83482e0293916dd82e553fa847482de52c39e06dc2d210
data/lib/brick/config.rb CHANGED
@@ -65,7 +65,7 @@ module Brick
65
65
  @mutex.synchronize { @additional_references = references }
66
66
  end
67
67
 
68
- # Associations to treat has a has_one
68
+ # Associations to treat as a has_one
69
69
  def has_ones
70
70
  @mutex.synchronize { @has_ones }
71
71
  end
@@ -80,6 +80,18 @@ module ActiveRecord
80
80
  end
81
81
  end
82
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
83
95
  end
84
96
 
85
97
  # Object.class_exec do
@@ -113,6 +125,11 @@ class Object
113
125
  singular_table_name = ActiveSupport::Inflector.underscore(model_name)
114
126
  table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
115
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
+
116
133
  # Maybe, just maybe there's a database table that will satisfy this need
117
134
  if (matching = [table_name, singular_table_name, plural_class_name, model_name].find { |m| relations.key?(m) })
118
135
  build_model(model_name, singular_table_name, table_name, relations, matching)
@@ -138,8 +155,9 @@ class Object
138
155
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
139
156
  raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
140
157
  end
141
- code = +"class #{model_name} < ActiveRecord::Base\n"
142
- 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|
143
161
  Object.const_set(model_name.to_sym, new_model_class)
144
162
  # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
145
163
  code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
@@ -176,8 +194,10 @@ class Object
176
194
  end
177
195
 
178
196
  fks = relation[:fks] || {}
179
- fks.each do |_constraint_name, assoc|
180
- 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]
181
201
  inverse_assoc_name = assoc[:inverse][:assoc_name]
182
202
  options = {}
183
203
  singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
@@ -185,7 +205,7 @@ class Object
185
205
  need_class_name = singular_table_name.underscore != assoc_name
186
206
  need_fk = "#{assoc_name}_id" != assoc[:fk]
187
207
  inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
188
- 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))
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))
189
209
  inverse_assoc_name = if has_ones[singular_inv_assoc_name]
190
210
  need_inverse_of = true
191
211
  has_ones[singular_inv_assoc_name]
@@ -215,23 +235,51 @@ class Object
215
235
  # Figure out if we need to specially call out the class_name and/or foreign key
216
236
  # (and if either of those then definitely also a specific inverse_of)
217
237
  options[:class_name] = singular_table_name.camelize if need_class_name
218
- options[:foreign_key] = assoc[:fk].to_sym if need_fk # Funky foreign key?
238
+ # Work around a bug in CPK where self-referencing belongs_to associations double up their foreign keys
239
+ if need_fk # Funky foreign key?
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
246
+ end
219
247
  options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk || need_inverse_of
220
- assoc_name = assoc_name.to_sym
221
- code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
222
- self.send(macro, assoc_name, **options)
223
248
 
224
- # Look for any valid "has_many :through"
249
+ # Prepare a list of entries for "has_many :through"
225
250
  if macro == :has_many
226
251
  relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
227
252
  next if k == assoc[:fk]
228
253
 
229
- hmt_fk = ActiveSupport::Inflector.pluralize(hmt_fk)
230
- code << " has_many :#{hmt_fk}, through: #{assoc_name.inspect}\n"
231
- self.send(:has_many, hmt_fk.to_sym, **{ through: assoc_name })
254
+ hmts[ActiveSupport::Inflector.pluralize(hmt_fk.last)] << [assoc, hmt_fk.first]
232
255
  end
233
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
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
234
281
  end
282
+
235
283
  code << "end # model #{model_name}\n\n"
236
284
  end # class definition
237
285
  [built_model, code]
@@ -275,7 +323,7 @@ class Object
275
323
  end
276
324
 
277
325
  def _brick_get_hm_assoc_name(relation, hm_assoc)
278
- if relation[:hm_counts][hm_assoc[:assoc_name]] > 1
326
+ if relation[:hm_counts][hm_assoc[:assoc_name]]&.> 1
279
327
  [ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name]), true]
280
328
  else
281
329
  [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
@@ -66,17 +66,23 @@ module Brick
66
66
  associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
67
67
  s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
68
68
  end
69
- hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>HM#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
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
70
  hms_columns = hms.each_with_object(+'') do |hm, s|
71
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 << "<td>
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>
78
79
  <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) unless #{obj_name}.#{hm.first}.count.zero? %>
79
80
  </td>\n"
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
80
86
  end
81
87
 
82
88
  inline = case args.first
@@ -89,7 +95,7 @@ module Brick
89
95
  <table id=\"#{table_name}\">
90
96
  <tr>
91
97
  <% is_first = true; is_need_id_col = nil
92
- 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(', ')} }
93
99
  @#{table_name}.columns.map(&:name).each do |col| %>
94
100
  <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
95
101
  <th>
@@ -125,9 +131,9 @@ module Brick
125
131
  <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
126
132
  <td>
127
133
  <% if (bt = bts[k]) %>
128
- <%= 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 %>
129
135
  <% elsif is_first %>
130
- <%= is_first = false; link_to val, #{obj_name} %>
136
+ <%= is_first = false; link_to val, #{obj_name}_path(#{obj_name}.#{pk}) %>
131
137
  <% else %>
132
138
  <%= val %>
133
139
  <% end %>
@@ -188,7 +194,7 @@ module Brick
188
194
  # Find associative tables that can be set up for has_many :through
189
195
  ::Brick.relations.each do |_key, tbl|
190
196
  tbl_cols = tbl[:cols].keys
191
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
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 }
192
198
  # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
193
199
  # foreign keys then it can act as an associative table and thus be used with has_many :through.
194
200
  if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - (tbl[:pkey].values.first || [])).length.zero?
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 5
8
+ TINY = 8
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
@@ -107,7 +111,7 @@ module Brick
107
111
  end
108
112
 
109
113
  s.first[a.foreign_key] = [a.name, a.klass]
110
- when :has_many
114
+ when :has_many, :has_one
111
115
  s.last[a.name] = a
112
116
  end
113
117
  s
@@ -192,7 +196,7 @@ module Brick
192
196
  end
193
197
  end
194
198
 
195
- # Associations to treat has a has_one
199
+ # Associations to treat as a has_one
196
200
  # @api public
197
201
  def has_ones=(hos)
198
202
  if hos
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.5
4
+ version: 1.0.8
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-19 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