brick 0.1.0 → 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 580f34a900e392172361e334170169b30c63f83e96bd7b3119e9d957d440d6b1
4
+ data.tar.gz: b02a7265b8010fe06d9599d353f7cdaf2435f872411fe8d6871fbd79e275ce19
5
+ SHA512:
6
+ metadata.gz: '038902e9a8bc77505c28cf0038ff6aa77d0440f389bbc43b29ffaccae4e344e37d1d8fa7a683aae44c5c36d6b51f282fcd22174539c47c8f30e162b3e9dc71b9'
7
+ data.tar.gz: f8e00496a7af527e47d75ef8a94cb02994224fa7fe536056b4399aac7758fdd3d3f3eddc364cbf6aec30efd4a56b852fa9982b7e06a48261a6bc9a178bc161fb
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'brick/serializers/yaml'
5
+
6
+ module Brick
7
+ # Global configuration affecting all threads. Some thread-specific
8
+ # configuration can be found in `brick.rb`, others in `controller.rb`.
9
+ class Config
10
+ include Singleton
11
+ attr_accessor :serializer, :version_limit, :association_reify_error_behaviour,
12
+ :object_changes_adapter, :root_model
13
+
14
+ def initialize
15
+ # Variables which affect all threads, whose access is synchronized.
16
+ @mutex = Mutex.new
17
+ @enabled = true
18
+
19
+ # Variables which affect all threads, whose access is *not* synchronized.
20
+ @serializer = Brick::Serializers::YAML
21
+ end
22
+
23
+ # Indicates whether Brick routes are on or off. Default: true.
24
+ def enable_routes
25
+ @mutex.synchronize { !!@enable_routes }
26
+ end
27
+
28
+ def enable_routes=(enable)
29
+ @mutex.synchronize { @enable_routes = enable }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,424 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Have markers on HM relationships to indicate "load this one every time" or "lazy load it" or "don't bother"
4
+ # Others on BT to indicate "this is a lookup"
5
+
6
+ # Mark specific tables as being lookups and they get put on the main screen as an editable thing
7
+ # If they relate to multiple different things (like looking up countries or something) then they only get edited from the main page, and importing new addresses can create a new country if needed.
8
+ # Indications of how relationships should operate will be useful soon (lookup is one kind, but probably more other kinds like this stuff makes a table or makes a list or who knows what.)
9
+ # Security must happen now -- at the model level, really low AR level automatically applied.
10
+
11
+ # Similar to .includes or .joins or something, bring in all records related through a HM, and include them in a trim way in a block of JSON
12
+ # Javascript thing that automatically makes nested table things from a block of hierarchical data (maybe sorta use one dimension of the crosstab thing)
13
+
14
+ # Finally incorporate the crosstab so that many dimensions can be set up as columns or rows and be made editable.
15
+
16
+ # X or Y axis can be made as driven by either columns or a row of data, so traditional table or crosstab can be shown, or a hybrid kind of thing of the two.
17
+
18
+ # Sensitive stuff -- make a lock icon thing so people don't accidentally edit stuff
19
+
20
+ # Static text that can go on pages - headings and footers and whatever
21
+ # Eventually some indication about if it should be a paginated table / unpaginated / a list of just some fields / etc
22
+
23
+ # Grid where each cell is one field and then when you mouse over then it shows a popup other table of detail inside
24
+
25
+ # DSL that describes the rows / columns and then what each cell can have, which could be nested related data, the specifics of X and Y driving things in the cell definition like a formula
26
+
27
+ # colour coded origins
28
+
29
+ # Drag TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = sectionn / etc
30
+
31
+
32
+ # ====================================
33
+ # Dynamically create generic templates
34
+ # ====================================
35
+
36
+ # For templates:
37
+ class ActionView::LookupContext
38
+ alias :_brick_template_exists? :template_exists?
39
+ def template_exists?(*args, **options)
40
+ x = _brick_template_exists?(*args, **options)
41
+ # Need to return true if we can fill in the blanks for a missing one
42
+ # args will be something like: ["index", ["categories"]]
43
+ unless x
44
+ relations = Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
45
+ matching = [views_name = args[1]&.first, views_name.singularize].find { |m| relations.key?(m) }
46
+ if (x = matching && (matching = relations[matching]) &&
47
+ (['index', 'show'].include?(args.first) || # Everything has index and show
48
+ # Only CRU stuff has create / update / destroy
49
+ (!matching.key?(:isView) && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
50
+ )
51
+ )
52
+ instance_variable_set(:@_brick_match, matching)
53
+ end
54
+ end
55
+ x
56
+ end
57
+
58
+ alias :_brick_find_template :find_template
59
+ def find_template(*args, **options)
60
+ if @_brick_match
61
+ inline = case args.first
62
+ when 'index'
63
+ # Something like: <%= @categories.inspect %>
64
+ "<%= @#{@_brick_match[:index]}.inspect %>"
65
+ when 'show'
66
+ "<%= @#{@_brick_match[:show]}.inspect %>"
67
+ end
68
+ # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
69
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
70
+ handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
71
+ ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
72
+ else
73
+ _brick_find_template(*args, **options)
74
+ end
75
+ end
76
+ end
77
+
78
+ # ==========================================================
79
+ # Dynamically create model or controller classes when needed
80
+ # ==========================================================
81
+
82
+ # Object.class_exec do
83
+ class Object
84
+ class << self
85
+ alias _brick_const_missing const_missing
86
+ def const_missing(*args)
87
+ return Object.const_get(args.first) if Object.const_defined?(args.first)
88
+
89
+ class_name = args.first.to_s
90
+ # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
91
+ # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
92
+ # that is, checking #qualified_name_for with: from_mod, const_name
93
+ # If we want to support namespacing in the future, might have to utilise something like this:
94
+ # path_suffix = ActiveSupport::Dependencies.qualified_name_for(Object, args.first).underscore
95
+ # return Object._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(path_suffix)
96
+ # If the file really exists, go and snag it:
97
+ return Object._brick_const_missing(*args) if ActiveSupport::Dependencies.search_for_file(class_name.underscore)
98
+
99
+ if class_name.end_with?('Controller') && (plural_class_name = class_name[0..-11]).length.positive?
100
+ # Otherwise now it's up to us to fill in the gaps
101
+ is_controller = true
102
+ table_name = ActiveSupport::Inflector.underscore(plural_class_name)
103
+ model_name = ActiveSupport::Inflector.singularize(plural_class_name)
104
+ singular_table_name = ActiveSupport::Inflector.singularize(table_name)
105
+ else # Model
106
+ # See if a file is there in the same way that ActiveSupport::Dependencies#load_missing_constant
107
+ # checks for it in ~/.rvm/gems/ruby-2.7.5/gems/activesupport-5.2.6.2/lib/active_support/dependencies.rb
108
+ plural_class_name = ActiveSupport::Inflector.pluralize(model_name = class_name)
109
+ singular_table_name = ActiveSupport::Inflector.underscore(model_name)
110
+ table_name = ActiveSupport::Inflector.pluralize(singular_table_name)
111
+ end
112
+ relations = Brick.instance_variable_get(:@relations)[ActiveRecord::Base.connection_pool.object_id] || {}
113
+ # Maybe, just maybe there's a database table that will satisfy this need
114
+ matches = [table_name, singular_table_name, plural_class_name, model_name]
115
+ if matching = matches.find { |m| relations.key?(m) }
116
+ built_class, code = if is_controller
117
+ build_controller(class_name, model_name, singular_table_name, table_name, relations, matching)
118
+ else
119
+ build_model(model_name, singular_table_name, table_name, relations, matching)
120
+ end
121
+ puts "#{code}end # #{ is_controller ? 'controller' : 'model' }\n\n"
122
+ built_class
123
+ else
124
+ puts "MISSING! #{args.inspect} #{table_name}"
125
+ Object._brick_const_missing(*args)
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def build_model(model_name, singular_table_name, table_name, relations, matching)
132
+ # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
133
+ if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
134
+ raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(class_name)}\".")
135
+ end
136
+ code = +"class #{model_name} < ActiveRecord::Base\n"
137
+ built_model = Class.new(ActiveRecord::Base) do |new_model_class|
138
+ Object.const_set(model_name.to_sym, new_model_class)
139
+ # Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
140
+ code << " self.table_name = '#{self.table_name = matching}'\n" unless table_name == matching
141
+
142
+ # By default, views get marked as read-only
143
+ if (relation = relations[matching]).key?(:isView)
144
+ self.define_method :'readonly?' do
145
+ true
146
+ end
147
+ end
148
+
149
+ # Missing a primary key column? (Usually "id")
150
+ ar_pks = primary_key.is_a?(String) ? [primary_key] : primary_key || []
151
+ db_pks = relation[:cols]&.map(&:first)
152
+ has_pk = ar_pks.length.positive? && (db_pks & ar_pks).sort == ar_pks.sort
153
+ our_pks = relation[:pkey].values.first
154
+ # No primary key, but is there anything UNIQUE?
155
+ # (Sort so that if there are multiple UNIQUE constraints we'll pick one that uses the least number of columns.)
156
+ our_pks = relation[:ukeys].values.sort { |a, b| a.length <=> b.length }.first unless our_pks&.present?
157
+ if has_pk
158
+ code << " # Primary key: #{ar_pks.join(', ')}\n" unless ar_pks == ['id']
159
+ elsif our_pks&.present?
160
+ if our_pks.length > 1 && respond_to?(:'primary_keys=') # Using the composite_primary_keys gem?
161
+ new_model_class.primary_keys = our_pks
162
+ code << " self.primary_keys = #{our_pks.map(&:to_sym).inspect}\n"
163
+ else
164
+ new_model_class.primary_key = (pk_sym = our_pks.first.to_sym)
165
+ code << " self.primary_key = #{pk_sym.inspect}\n"
166
+ end
167
+ else
168
+ code << " # Could not identify any column(s) to use as a primary key\n"
169
+ end
170
+
171
+ # if relation[:cols].key?('last_update')
172
+ # define_method :updated_at do
173
+ # last_update
174
+ # end
175
+ # define_method :'updated_at=' do |val|
176
+ # last_update=(val)
177
+ # end
178
+ # end
179
+
180
+ fks = relation[:fks] || {}
181
+ fks.each do |_constraint_name, assoc|
182
+ assoc_name = assoc[:assoc_name]
183
+ inverse_assoc_name = assoc[:inverse][:assoc_name]
184
+ options = {}
185
+ singular_table_name = ActiveSupport::Inflector.singularize(assoc[:inverse_table])
186
+ macro = if assoc[:is_bt]
187
+ need_class_name = singular_table_name.underscore != assoc_name
188
+ need_fk = "#{assoc_name}_id" != assoc[:fk]
189
+ inverse_assoc_name, _x = _brick_get_hm_assoc_name(relations[assoc[:inverse_table]], assoc[:inverse])
190
+ :belongs_to
191
+ else
192
+ # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
193
+ # Are there multiple foreign keys out to the same table?
194
+ assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
195
+ need_fk = "#{singular_table_name}_id" != assoc[:fk]
196
+ # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
197
+ :has_many
198
+ end
199
+ options[:class_name] = singular_table_name.camelize if need_class_name
200
+ # Figure out if we need to specially call out the foreign key
201
+ if need_fk # Funky foreign key?
202
+ options[:foreign_key] = assoc[:fk].to_sym
203
+ end
204
+ options[:inverse_of] = inverse_assoc_name.to_sym if need_class_name || need_fk
205
+ assoc_name = assoc_name.to_sym
206
+ code << " #{macro} #{assoc_name.inspect}#{options.map { |k, v| ", #{k}: #{v.inspect}" }.join}\n"
207
+ self.send(macro, assoc_name, **options)
208
+
209
+ # Look for any valid "has_many :through"
210
+ if macro == :has_many
211
+ relations[assoc[:inverse_table]][:hmt_fks].each do |k, hmt_fk|
212
+ next if k == assoc[:fk]
213
+
214
+ hmt_fk = ActiveSupport::Inflector.pluralize(hmt_fk)
215
+ code << " has_many :#{hmt_fk}, through: #{assoc_name.inspect}\n"
216
+ self.send(:has_many, hmt_fk.to_sym, **{ through: assoc_name })
217
+ end
218
+ end
219
+ end
220
+ end # class definition
221
+ [built_model, code]
222
+ end
223
+
224
+
225
+ def build_controller(class_name, model_name, singular_table_name, table_name, relations, matching)
226
+ code = +"class #{class_name} < ApplicationController\n"
227
+ built_controller = Class.new(ActionController::Base) do |new_controller_class|
228
+ Object.const_set(class_name.to_sym, new_controller_class)
229
+
230
+ model = model_name.constantize
231
+ code << " def index\n"
232
+ code << " @#{table_name} = #{model_name}#{model.primary_key ? ".order(#{model.primary_key.inspect}" : '.all'})\n"
233
+ code << " end\n"
234
+ self.define_method :index do
235
+ relation = model.primary_key ? model.order(model.primary_key) : model.all
236
+ instance_variable_set("@#{table_name}".to_sym, relation)
237
+ end
238
+
239
+ if model.primary_key
240
+ code << " def show\n"
241
+ code << " @#{singular_table_name} = #{model_name}.find(params[:id].split(','))\n"
242
+ code << " end\n"
243
+ self.define_method :show do
244
+ instance_variable_set("@#{singular_table_name}".to_sym, model.find(params[:id].split(',')))
245
+ end
246
+ end
247
+
248
+ # By default, views get marked as read-only
249
+ unless (relation = relations[matching]).key?(:isView)
250
+ code << " # (Define :new, :create, :edit, :update, and :destroy)\n"
251
+ end
252
+ end # class definition
253
+ [built_controller, code]
254
+ end
255
+
256
+ def _brick_get_hm_assoc_name(relation, hm_assoc)
257
+ if relation[:hm_counts][hm_assoc[:assoc_name]] > 1
258
+ [ActiveSupport::Inflector.pluralize(hm_assoc[:alternate_name]), true]
259
+ else
260
+ [ActiveSupport::Inflector.pluralize(hm_assoc[:inverse_table]), nil]
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ # ==========================================================
267
+ # Get info on all relations during first database connection
268
+ # ==========================================================
269
+
270
+ module ActiveRecord::ConnectionHandling
271
+ alias old_establish_connection establish_connection
272
+ def establish_connection(*args)
273
+ connections = Brick.instance_variable_get(:@relations) ||
274
+ Brick.instance_variable_set(:@relations, (connections = {}))
275
+ # puts connections.inspect
276
+ x = old_establish_connection(*args)
277
+ # Key our list of relations for this connection off of the connection pool's object_id
278
+ relations = (connections[ActiveRecord::Base.connection_pool.object_id] ||= Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } })
279
+
280
+ if relations.empty?
281
+ schema = 'public'
282
+ puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
283
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, [
284
+ "SELECT t.table_name AS relation_name, t.table_type,
285
+ c.column_name, c.data_type,
286
+ COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
287
+ tc.constraint_type AS const, kcu.constraint_name AS key
288
+ FROM INFORMATION_SCHEMA.tables AS t
289
+ LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
290
+ AND t.table_name = c.table_name
291
+ LEFT OUTER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu ON
292
+ -- ON kcu.CONSTRAINT_CATALOG = t.table_catalog AND
293
+ kcu.CONSTRAINT_SCHEMA = c.table_schema
294
+ AND kcu.TABLE_NAME = c.table_name
295
+ AND kcu.position_in_unique_constraint IS NULL
296
+ AND kcu.ordinal_position = c.ordinal_position
297
+ LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
298
+ ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
299
+ AND kcu.CONSTRAINT_NAME = tc.constraint_name
300
+ WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
301
+ -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
302
+ AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
303
+ ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
304
+ ])
305
+ ActiveRecord::Base.connection.execute(sql).each do |r|
306
+ # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
307
+
308
+ relation = relations[r['relation_name']]
309
+ relation[:index] = r['relation_name'].underscore
310
+ relation[:show] = relation[:index].singularize
311
+ relation[:index] = relation[:index].pluralize
312
+ relation[:isView] = true if r['table_type'] == 'VIEW'
313
+ col_name = r['column_name']
314
+ cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
315
+ key = case r['const']
316
+ when 'PRIMARY KEY'
317
+ relation[:pkey][r['key']] ||= []
318
+ when 'UNIQUE'
319
+ relation[:ukeys][r['key']] ||= []
320
+ # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
321
+ # key[r['key']]
322
+ end
323
+ key << col_name if key
324
+ cols[col_name] = [r['data_type'], r['max_length'], r['measures']&.include?(col_name)]
325
+ # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
326
+ end
327
+
328
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, [
329
+ "SELECT kcu1.CONSTRAINT_NAME, kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME
330
+ FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
331
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
332
+ ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
333
+ AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
334
+ AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
335
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
336
+ ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
337
+ AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
338
+ AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
339
+ AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
340
+ WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
341
+ # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
342
+ ])
343
+ ActiveRecord::Base.connection.execute(sql).values.each do |fk|
344
+ bt_assoc_name = fk[2].underscore
345
+ bt_assoc_name = bt_assoc_name[0..-4] if bt_assoc_name.end_with?('_id')
346
+
347
+ bts = (relation = relations[fk[1]]).fetch(:fks) { relation[:fks] = {} }
348
+ if (assoc_bt = bts[fk[0]])
349
+ assoc_bt[:fk] = assoc_bt[:fk].is_a?(String) ? [assoc_bt[:fk], fk[2]] : assoc_bt[:fk].concat(fk[2])
350
+ assoc_bt[:assoc_name] = "#{assoc_bt[:assoc_name]}_#{fk[2]}"
351
+ else
352
+ assoc_bt = bts[fk[0]] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: fk[3] }
353
+ end
354
+
355
+ hms = (relation = relations[fk[3]]).fetch(:fks) { relation[:fks] = {} }
356
+ if (assoc_hm = hms[fk[0]])
357
+ assoc_hm[:fk] = assoc_hm[:fk].is_a?(String) ? [assoc_hm[:fk], fk[2]] : assoc_hm[:fk].concat(fk[2])
358
+ assoc_hm[:alternate_name] = "#{assoc_hm[:alternate_name]}_#{bt_assoc_name}" unless assoc_hm[:alternate_name] == bt_assoc_name
359
+ assoc_hm[:inverse] = assoc_bt
360
+ else
361
+ assoc_hm = hms[fk[0]] = { is_bt: false, fk: fk[2], assoc_name: fk[1], alternate_name: bt_assoc_name, inverse_table: fk[1], inverse: assoc_bt }
362
+ hm_counts = relation.fetch(:hm_counts) { relation[:hm_counts] = {} }
363
+ hm_counts[fk[1]] = hm_counts.fetch(fk[1]) { 0 } + 1
364
+ end
365
+ assoc_bt[:inverse] = assoc_hm
366
+ # hms[fk[0]] << { is_bt: false, fk: fk[2], assoc_name: fk[1], alternate_name: bt_assoc_name, inverse_table: fk[1] }
367
+ end
368
+ # Find associative tables that can be set up for has_many :through
369
+ relations.each do |_key, tbl|
370
+ tbl_cols = tbl[:cols].keys
371
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
372
+ # Aside from the primary key and created_at, updated_at,This table has only foreign keys?
373
+ if fks.length > 1 && (tbl_cols - fks.keys - ['created_at', 'updated_at', 'deleted_at', 'last_update'] - tbl[:pkey].values.first).length.zero?
374
+ fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
375
+ end
376
+ end
377
+ end
378
+
379
+ puts "Classes built from tables:"
380
+ relations.select { |_k, v| !v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
381
+ puts "Classes built from views:"
382
+ relations.select { |_k, v| v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
383
+ # pp relations; nil
384
+
385
+ # relations.keys.each { |k| ActiveSupport::Inflector.singularize(k).camelize.constantize }
386
+ # Layout table describes permissioned hierarchy throughout
387
+ x
388
+ end
389
+ end
390
+
391
+ # ==========================================
392
+
393
+ # :nodoc:
394
+ module Brick
395
+ # rubocop:disable Style/CommentedKeyword
396
+ module Extensions
397
+ MAX_ID = Arel.sql('MAX(id)')
398
+ IS_AMOEBA = Gem.loaded_specs['amoeba']
399
+
400
+ def self.included(base)
401
+ base.send :extend, ClassMethods
402
+ end
403
+
404
+ # :nodoc:
405
+ module ClassMethods
406
+
407
+ private
408
+
409
+ def _create_class()
410
+ end
411
+ end
412
+ end # module Extensions
413
+ # rubocop:enable Style/CommentedKeyword
414
+
415
+ # Rails < 4.0 doesn't have ActiveRecord::RecordNotUnique, so use the more generic ActiveRecord::ActiveRecordError instead
416
+ ar_not_unique_error = ActiveRecord.const_defined?('RecordNotUnique') ? ActiveRecord::RecordNotUnique : ActiveRecord::ActiveRecordError
417
+ class NoUniqueColumnError < ar_not_unique_error
418
+ end
419
+
420
+ # Rails < 4.2 doesn't have ActiveRecord::RecordInvalid, so use the more generic ActiveRecord::ActiveRecordError instead
421
+ ar_invalid_error = ActiveRecord.const_defined?('RecordInvalid') ? ActiveRecord::RecordInvalid : ActiveRecord::ActiveRecordError
422
+ class LessThanHalfAreMatchingColumnsError < ar_invalid_error
423
+ end
424
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # before hook for Cucumber
4
+ Before do
5
+ # Brick.enable_routes = true
6
+ # Brick.enable_models = true
7
+ # Brick.enable_controllers = true
8
+ # Brick.enable_views = true
9
+ Brick.request.whodunnit = nil
10
+ Brick.request.controller_info = {} if defined?(::Rails)
11
+ end
12
+
13
+ module Brick
14
+ module Cucumber
15
+ # Helper method for disabling Brick in Cucumber features
16
+ module Extensions
17
+ def without_brick
18
+ was_enable_routes = ::Brick.enable_routes?
19
+ # was_enable_models = ::Brick.enable_models?
20
+ # was_enable_controllers = ::Brick.enable_controllers?
21
+ # was_enable_views = ::Brick.enable_views?
22
+ ::Brick.enable_routes = false
23
+ # ::Brick.enable_models = false
24
+ # ::Brick.enable_controllers = false
25
+ # ::Brick.enable_views = false
26
+ begin
27
+ yield
28
+ ensure
29
+ ::Brick.enable_routes = was_enable_routes
30
+ # ::Brick.enable_models = was_enable_models
31
+ # ::Brick.enable_controllers = was_enable_controllers
32
+ # ::Brick.enable_views = was_enable_views
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ World Brick::Cucumber::Extensions
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brick
4
+ module Rails
5
+ # Extensions to rails controllers. Provides convenient ways to pass certain
6
+ # information to the model layer, with `controller_info` and `whodunnit`.
7
+ # Also includes a convenient on/off switch,
8
+ # `brick_enabled_for_controller`.
9
+ module Controller
10
+ def self.included(controller)
11
+ controller.before_action(
12
+ :set_brick_enabled_for_controller,
13
+ :set_brick_controller_info
14
+ )
15
+ end
16
+
17
+ protected
18
+
19
+ # Returns the user who is responsible for any changes that occur.
20
+ # By default this calls `current_user` and returns the result.
21
+ #
22
+ # Override this method in your controller to call a different
23
+ # method, e.g. `current_person`, or anything you like.
24
+ #
25
+ # @api public
26
+ def user_for_brick
27
+ return unless defined?(current_user)
28
+
29
+ ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
30
+ rescue NoMethodError
31
+ current_user
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ if defined?(::ActionController)
38
+ ::ActiveSupport.on_load(:action_controller) do
39
+ include ::Brick::Rails::Controller
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brick
4
+ module Rails
5
+ # See http://guides.rubyonrails.org/engines.html
6
+ class Engine < ::Rails::Engine
7
+ # paths['app/models'] << 'lib/brick/frameworks/active_record/models'
8
+ config.brick = ActiveSupport::OrderedOptions.new
9
+ initializer 'brick.initialisation' do |app|
10
+ # Auto-routing behaviour
11
+ if (::Brick.enable_routes = app.config.brick.fetch(:enable_routes, true))
12
+ ::Brick.append_routes
13
+ end
14
+ # Brick.enable_models = app.config.brick.fetch(:enable_models, true)
15
+ # Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
16
+ # Brick.enable_views = app.config.brick.fetch(:enable_views, true)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'brick/frameworks/rails/controller'
4
+ require 'brick/frameworks/rails/engine'
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/matchers'
5
+ # require 'brick/frameworks/rspec/helpers'
6
+
7
+ RSpec.configure do |config|
8
+ # config.include ::Brick::RSpec::Helpers::InstanceMethods
9
+ # config.extend ::Brick::RSpec::Helpers::ClassMethods
10
+
11
+ # config.before(:each) do
12
+ # ::Brick.enable_routes = false
13
+ # end
14
+
15
+ # config.before(:each, df_importing: true) do
16
+ # ::Brick.enable_routes = true
17
+ # end
18
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brick
4
+ module Serializers
5
+ # An alternate serializer for, e.g. `versions.object`.
6
+ module JSON
7
+ extend self # makes all instance methods become module methods as well
8
+
9
+ def load(string)
10
+ ActiveSupport::JSON.decode string
11
+ end
12
+
13
+ def dump(object)
14
+ ActiveSupport::JSON.encode object
15
+ end
16
+
17
+ # Returns a SQL LIKE condition to be used to match the given field and
18
+ # value in the serialized object.
19
+ def where_object_condition(arel_field, field, value)
20
+ # Convert to JSON to handle strings and nulls correctly.
21
+ json_value = value.to_json
22
+
23
+ # If the value is a number, we need to ensure that we find the next
24
+ # character too, which is either `,` or `}`, to ensure that searching
25
+ # for the value 12 doesn't yield false positives when the value is
26
+ # 123.
27
+ if value.is_a? Numeric
28
+ arel_field.matches("%\"#{field}\":#{json_value},%")
29
+ .or(arel_field.matches("%\"#{field}\":#{json_value}}%"))
30
+ else
31
+ arel_field.matches("%\"#{field}\":#{json_value}%")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Brick
6
+ module Serializers
7
+ # The default serializer for, e.g. `versions.object`.
8
+ module YAML
9
+ extend self # makes all instance methods become module methods as well
10
+
11
+ def load(string)
12
+ ::YAML.safe_load string
13
+ end
14
+
15
+ def dump(object)
16
+ ::YAML.dump object
17
+ end
18
+
19
+ # Returns a SQL LIKE condition to be used to match the given field and
20
+ # value in the serialized object.
21
+ def where_object_condition(arel_field, field, value)
22
+ arel_field.matches("%\n#{field}: #{value}\n%")
23
+ end
24
+ end
25
+ end
26
+ end