brick 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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