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 +4 -4
- data/lib/brick/config.rb +9 -0
- data/lib/brick/extensions.rb +96 -26
- data/lib/brick/frameworks/rails/engine.rb +102 -121
- data/lib/brick/version_number.rb +1 -1
- data/lib/brick.rb +50 -2
- data/lib/generators/brick/install_generator.rb +8 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b65cbc8bbf472c887136395afb13f9dcf6e460f0c279dda0372939da8863178f
|
4
|
+
data.tar.gz: d63ae1e8795bb788e67c05d98f245c086aa773fbfc5585f716e3371652976878
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/brick/extensions.rb
CHANGED
@@ -61,14 +61,15 @@ module ActiveRecord
|
|
61
61
|
wheres = {}
|
62
62
|
rel_joins = []
|
63
63
|
params.each do |k, v|
|
64
|
-
|
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
|
-
|
141
|
-
|
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
|
-
|
179
|
-
|
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
|
-
|
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
|
-
#
|
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].
|
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
|
-
#
|
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
|
-
|
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]]
|
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
|
-
|
342
|
+
conn = _brick_establish_connection(*args)
|
343
|
+
_brick_reflect_tables
|
344
|
+
conn
|
345
|
+
end
|
277
346
|
|
278
|
-
|
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
|
-
|
393
|
-
|
394
|
-
|
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
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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.
|
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
|
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
|
-
|
165
|
-
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
data/lib/brick/version_number.rb
CHANGED
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=(
|
163
|
-
|
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
|
+
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-
|
11
|
+
date: 2022-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|