brick 1.0.1 → 1.0.4

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: 1b4733a804fae7a4759eae48690541f1873cb81fd1b2a9986f849202c1c76c5d
4
- data.tar.gz: d721f215cfed0473804f82969fd3c89dd7a94671d24b0f44b0e03cc73d787754
3
+ metadata.gz: fe6bf3e38aef34261d9398b25f804504e7152721673b0534253c4284c9018013
4
+ data.tar.gz: 766af0df2edc3dc8c8c73a5d1ecd99c8d738ed7cd00fc21c79c38ac3db991819
5
5
  SHA512:
6
- metadata.gz: b73c787a9c7aa6804d2cfce77921252dd77ce3e6e3710bd30875436d0f66011e6043e0ae4cbdaaeff6f4abb725d8051b5c1be139ee28b80648314f124ff7204f
7
- data.tar.gz: 000b467728db85565d793df6e22429e3ff804bb7d39e5c4d6346becc24bc940ec9476fe00758d1e4a4fad868239e755ba7bdef596ecb115f358332656fd1be5e
6
+ metadata.gz: 40f2eacaf7fada3056f5bfc701b01441ad02e34e76e87dc8e53b36a546c87216e8a319c3f0763b8fecff49344224d5660ff5b875937a3e30e2e7c80c662f684c
7
+ data.tar.gz: 7901020af2ec1759c27c16a0c3a3ffbcddcd09691c652c14c02781823964e36409533afc562fb0d9beb508cc3329b601d6fe8c266c101f6db6d93aad5e055a3e
data/lib/brick/config.rb CHANGED
@@ -57,12 +57,36 @@ module Brick
57
57
  end
58
58
 
59
59
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
60
+ def additional_references
61
+ @mutex.synchronize { @additional_references }
62
+ end
63
+
60
64
  def additional_references=(references)
61
65
  @mutex.synchronize { @additional_references = references }
62
66
  end
63
67
 
64
- def additional_references
65
- @mutex.synchronize { @additional_references }
68
+ def skip_database_views
69
+ @mutex.synchronize { @skip_database_views }
70
+ end
71
+
72
+ def skip_database_views=(disable)
73
+ @mutex.synchronize { @skip_database_views = disable }
74
+ end
75
+
76
+ def exclude_tables
77
+ @mutex.synchronize { @exclude_tables }
78
+ end
79
+
80
+ def exclude_tables=(value)
81
+ @mutex.synchronize { @exclude_tables = value }
82
+ end
83
+
84
+ def metadata_columns
85
+ @mutex.synchronize { @metadata_columns }
86
+ end
87
+
88
+ def metadata_columns=(columns)
89
+ @mutex.synchronize { @metadata_columns = columns }
66
90
  end
67
91
  end
68
92
  end
@@ -26,16 +26,58 @@
26
26
 
27
27
  # colour coded origins
28
28
 
29
- # Drag TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = sectionn / etc
29
+ # Drag something like TmfModel#name onto the rows and have it automatically add five columns -- where type=zone / where type = section / etc
30
+
31
+ # Support for Postgres / MySQL enums (add enum to model, use model enums to make a drop-down in the UI)
32
+
33
+ # Currently quadrupling up routes
30
34
 
31
35
  # ==========================================================
32
36
  # Dynamically create model or controller classes when needed
33
37
  # ==========================================================
34
38
 
35
39
  # By default all models indicate that they are not views
36
- class ActiveRecord::Base
37
- def self.is_view?
38
- false
40
+ module ActiveRecord
41
+ class Base
42
+ def self.is_view?
43
+ false
44
+ end
45
+
46
+ # Used to show a little prettier name for an object
47
+ def brick_descrip
48
+ klass = self.class
49
+ klass.primary_key ? "#{klass.name} ##{send(klass.primary_key)}" : to_s
50
+ end
51
+
52
+ private
53
+
54
+ def self._brick_get_fks
55
+ @_brick_get_fks ||= reflect_on_all_associations.select { |a2| a2.macro == :belongs_to }.map(&:foreign_key)
56
+ end
57
+ end
58
+
59
+ class Relation
60
+ def brick_where(params)
61
+ wheres = {}
62
+ rel_joins = []
63
+ params.each do |k, v|
64
+ if (ks = k.split('.')).length > 1
65
+ assoc_name = ks.first.to_sym
66
+ # Make sure it's a good association name and that the model has that column name
67
+ next unless klass.reflect_on_association(assoc_name)&.klass&.columns&.map(&:name)&.include?(ks.last)
68
+
69
+ rel_joins << assoc_name unless rel_joins.include?(assoc_name)
70
+ else
71
+ next unless klass._brick_get_fks.include?(k)
72
+ end
73
+ wheres[k] = v.split(',')
74
+ end
75
+ unless wheres.empty?
76
+ where!(wheres)
77
+ joins!(rel_joins) unless rel_joins.empty?
78
+ wheres # Return the specific parameters that we did use
79
+ end
80
+ end
39
81
  end
40
82
  end
41
83
 
@@ -88,6 +130,9 @@ class Object
88
130
  private
89
131
 
90
132
  def build_model(model_name, singular_table_name, table_name, relations, matching)
133
+ return if ((is_view = (relation = relations[matching]).key?(:isView)) && ::Brick.config.skip_database_views) ||
134
+ ::Brick.config.exclude_tables.include?(matching)
135
+
91
136
  # Are they trying to use a pluralised class name such as "Employees" instead of "Employee"?
92
137
  if table_name == singular_table_name && !ActiveSupport::Inflector.inflections.uncountable.include?(table_name)
93
138
  raise NameError.new("Class name for a model that references table \"#{matching}\" should be \"#{ActiveSupport::Inflector.singularize(model_name)}\".")
@@ -100,7 +145,7 @@ class Object
100
145
 
101
146
  # Override models backed by a view so they return true for #is_view?
102
147
  # (Dynamically-created controllers and view templates for such models will then act in a read-only way)
103
- if (is_view = (relation = relations[matching]).key?(:isView))
148
+ if is_view
104
149
  new_model_class.define_singleton_method :'is_view?' do
105
150
  true
106
151
  end
@@ -129,15 +174,6 @@ class Object
129
174
  code << " # Could not identify any column(s) to use as a primary key\n" unless is_view
130
175
  end
131
176
 
132
- # if relation[:cols].key?('last_update')
133
- # define_method :updated_at do
134
- # last_update
135
- # end
136
- # define_method :'updated_at=' do |val|
137
- # last_update=(val)
138
- # end
139
- # end
140
-
141
177
  fks = relation[:fks] || {}
142
178
  fks.each do |_constraint_name, assoc|
143
179
  assoc_name = assoc[:assoc_name]
@@ -153,7 +189,7 @@ class Object
153
189
  # need_class_name = ActiveSupport::Inflector.singularize(assoc_name) == ActiveSupport::Inflector.singularize(table_name.underscore)
154
190
  # Are there multiple foreign keys out to the same table?
155
191
  assoc_name, need_class_name = _brick_get_hm_assoc_name(relation, assoc)
156
- need_fk = "#{singular_table_name}_id" != assoc[:fk]
192
+ need_fk = "#{ActiveSupport::Inflector.singularize(assoc[:inverse][:inverse_table])}_id" != assoc[:fk]
157
193
  # fks[table_name].find { |other_assoc| other_assoc.object_id != assoc.object_id && other_assoc[:assoc_name] == assoc[assoc_name] }
158
194
  :has_many
159
195
  end
@@ -183,7 +219,6 @@ class Object
183
219
  [built_model, code]
184
220
  end
185
221
 
186
-
187
222
  def build_controller(class_name, plural_class_name, model, relations)
188
223
  table_name = ActiveSupport::Inflector.underscore(plural_class_name)
189
224
  singular_table_name = ActiveSupport::Inflector.singularize(table_name)
@@ -193,10 +228,12 @@ class Object
193
228
  Object.const_set(class_name.to_sym, new_controller_class)
194
229
 
195
230
  code << " def index\n"
196
- code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect}" : '.all'})\n"
231
+ code << " @#{table_name} = #{model.name}#{model.primary_key ? ".order(#{model.primary_key.inspect})" : '.all'}\n"
232
+ code << " @#{table_name}.brick_where(params)\n"
197
233
  code << " end\n"
198
234
  self.define_method :index do
199
235
  ar_relation = model.primary_key ? model.order(model.primary_key) : model.all
236
+ instance_variable_set(:@_brick_params, ar_relation.brick_where(params))
200
237
  instance_variable_set("@#{table_name}".to_sym, ar_relation)
201
238
  end
202
239
 
@@ -234,19 +271,36 @@ end
234
271
  # ==========================================================
235
272
 
236
273
  module ActiveRecord::ConnectionHandling
237
- alias old_establish_connection establish_connection
274
+ alias _brick_establish_connection establish_connection
238
275
  def establish_connection(*args)
239
- # puts connections.inspect
240
- x = old_establish_connection(*args)
276
+ x = _brick_establish_connection(*args)
241
277
 
242
278
  if (relations = ::Brick.relations).empty?
243
- schema = 'public'
244
- puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
245
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
279
+ # Only for Postgres? (Doesn't work in sqlite3)
280
+ # puts ActiveRecord::Base.connection.execute("SELECT current_setting('SEARCH_PATH')").to_a.inspect
281
+
282
+ case ActiveRecord::Base.connection.adapter_name
283
+ when 'PostgreSQL'
284
+ schema = 'public'
285
+ when 'Mysql2'
286
+ schema = ActiveRecord::Base.connection.current_database
287
+ when 'SQLite'
288
+ sql = "SELECT m.name AS relation_name, UPPER(m.type) AS table_type,
289
+ p.name AS column_name, p.type AS data_type,
290
+ CASE p.pk WHEN 1 THEN 'PRIMARY KEY' END AS const
291
+ FROM sqlite_master AS m
292
+ INNER JOIN pragma_table_info(m.name) AS p
293
+ WHERE m.name NOT IN ('ar_internal_metadata', 'schema_migrations')
294
+ ORDER BY m.name, p.cid"
295
+ else
296
+ puts "Unfamiliar with connection adapter #{ActiveRecord::Base.connection.adapter_name}"
297
+ end
298
+
299
+ sql ||= ActiveRecord::Base.send(:sanitize_sql_array, [
246
300
  "SELECT t.table_name AS relation_name, t.table_type,
247
301
  c.column_name, c.data_type,
248
302
  COALESCE(c.character_maximum_length, c.numeric_precision) AS max_length,
249
- tc.constraint_type AS const, kcu.constraint_name AS key
303
+ tc.constraint_type AS const, kcu.constraint_name AS \"key\"
250
304
  FROM INFORMATION_SCHEMA.tables AS t
251
305
  LEFT OUTER JOIN INFORMATION_SCHEMA.columns AS c ON t.table_schema = c.table_schema
252
306
  AND t.table_name = c.table_name
@@ -258,66 +312,92 @@ module ActiveRecord::ConnectionHandling
258
312
  AND kcu.ordinal_position = c.ordinal_position
259
313
  LEFT OUTER JOIN INFORMATION_SCHEMA.table_constraints AS tc
260
314
  ON kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
315
+ AND kcu.TABLE_NAME = tc.TABLE_NAME
261
316
  AND kcu.CONSTRAINT_NAME = tc.constraint_name
262
317
  WHERE t.table_schema = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')
263
318
  -- AND t.table_type IN ('VIEW') -- 'BASE TABLE', 'FOREIGN TABLE'
264
319
  AND t.table_name NOT IN ('pg_stat_statements', 'ar_internal_metadata', 'schema_migrations')
265
320
  ORDER BY 1, t.table_type DESC, c.ordinal_position", schema
266
321
  ])
267
- ActiveRecord::Base.connection.execute(sql).each do |r|
268
- # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
269
-
270
- relation = relations[r['relation_name']]
271
- relation[:index] = r['relation_name'].underscore
272
- relation[:show] = relation[:index].singularize
273
- relation[:index] = relation[:index].pluralize
274
- relation[:isView] = true if r['table_type'] == 'VIEW'
275
- col_name = r['column_name']
276
- cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
277
- key = case r['const']
278
- when 'PRIMARY KEY'
279
- relation[:pkey][r['key']] ||= []
280
- when 'UNIQUE'
281
- relation[:ukeys][r['key']] ||= []
282
- # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
283
- # key[r['key']]
284
- end
285
- key << col_name if key
286
- cols[col_name] = [r['data_type'], r['max_length'], r['measures']&.include?(col_name)]
287
- # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
288
- end
289
322
 
290
- sql = ActiveRecord::Base.send(:sanitize_sql_array, [
291
- "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME, kcu1.CONSTRAINT_NAME
292
- FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
293
- INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
294
- ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
295
- AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
296
- AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
297
- INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
298
- ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
299
- AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
300
- AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
301
- AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
302
- WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
303
- # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
304
- ])
305
- ActiveRecord::Base.connection.execute(sql).values.each { |fk| ::Brick._add_bt_and_hm(fk, relations) }
306
-
307
- # Find associative tables that can be set up for has_many :through
308
- relations.each do |_key, tbl|
309
- tbl_cols = tbl[:cols].keys
310
- fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
311
- # Aside from the primary key and created_at, updated_at,This table has only foreign keys?
312
- if fks.length > 1 && (tbl_cols - fks.keys - ['created_at', 'updated_at', 'deleted_at', 'last_update'] - tbl[:pkey].values.first).length.zero?
313
- fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
323
+ measures = []
324
+ case ActiveRecord::Base.connection.adapter_name
325
+ when 'PostgreSQL', 'SQLite' # These bring back a hash for each row because the query uses column aliases
326
+ ActiveRecord::Base.connection.execute(sql).each do |r|
327
+ # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
328
+ relation = relations[(relation_name = r['relation_name'])]
329
+ relation[:isView] = true if r['table_type'] == 'VIEW'
330
+ col_name = r['column_name']
331
+ key = case r['const']
332
+ when 'PRIMARY KEY'
333
+ relation[:pkey][r['key'] || relation_name] ||= []
334
+ when 'UNIQUE'
335
+ relation[:ukeys][r['key'] || "#{relation_name}.#{col_name}"] ||= []
336
+ # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
337
+ # key[r['key']]
338
+ end
339
+ key << col_name if key
340
+ cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
341
+ cols[col_name] = [r['data_type'], r['max_length'], measures&.include?(col_name)]
342
+ # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
314
343
  end
344
+ else # MySQL2 acts a little differently, bringing back an array for each row
345
+ ActiveRecord::Base.connection.execute(sql).each do |r|
346
+ # next if internal_views.include?(r['relation_name']) # Skip internal views such as v_all_assessments
347
+ relation = relations[(relation_name = r[0])] # here relation represents a table or view from the database
348
+ relation[:isView] = true if r[1] == 'VIEW' # table_type
349
+ col_name = r[2]
350
+ key = case r[5] # constraint type
351
+ when 'PRIMARY KEY'
352
+ # key
353
+ relation[:pkey][r[6] || relation_name] ||= []
354
+ when 'UNIQUE'
355
+ relation[:ukeys][r[6] || "#{relation_name}.#{col_name}"] ||= []
356
+ # key = (relation[:ukeys] = Hash.new { |h, k| h[k] = [] }) if key.is_a?(Array)
357
+ # key[r['key']]
358
+ end
359
+ key << col_name if key
360
+ cols = relation[:cols] # relation.fetch(:cols) { relation[:cols] = [] }
361
+ # 'data_type', 'max_length'
362
+ cols[col_name] = [r[3], r[4], measures&.include?(col_name)]
363
+ # puts "KEY! #{r['relation_name']}.#{col_name} #{r['key']} #{r['const']}" if r['key']
364
+ end
365
+ end
366
+
367
+ case ActiveRecord::Base.connection.adapter_name
368
+ when 'PostgreSQL', 'Mysql2'
369
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, [
370
+ "SELECT kcu1.TABLE_NAME, kcu1.COLUMN_NAME, kcu2.TABLE_NAME, kcu1.CONSTRAINT_NAME
371
+ FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
372
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu1
373
+ ON kcu1.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG
374
+ AND kcu1.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
375
+ AND kcu1.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
376
+ INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu2
377
+ ON kcu2.CONSTRAINT_CATALOG = rc.UNIQUE_CONSTRAINT_CATALOG
378
+ AND kcu2.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA
379
+ AND kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
380
+ AND kcu2.ORDINAL_POSITION = kcu1.ORDINAL_POSITION
381
+ WHERE kcu1.CONSTRAINT_SCHEMA = ? -- COALESCE(current_setting('SEARCH_PATH'), 'public')", schema
382
+ # AND kcu2.TABLE_NAME = ?;", Apartment::Tenant.current, table_name
383
+ ])
384
+ when 'SQLite'
385
+ sql = "SELECT m.name, fkl.\"from\", fkl.\"table\", m.name || '_' || fkl.\"from\" AS constraint_name
386
+ FROM sqlite_master m
387
+ INNER JOIN pragma_foreign_key_list(m.name) fkl ON m.type = 'table'
388
+ ORDER BY m.name, fkl.seq"
389
+ else
390
+ end
391
+ if sql
392
+ result = ActiveRecord::Base.connection.execute(sql)
393
+ result = result.values unless result.is_a?(Array)
394
+ result.each { |fk| ::Brick._add_bt_and_hm(fk, relations) }
315
395
  end
316
396
  end
317
397
 
318
- puts "Classes built from tables:"
398
+ puts "Classes that can be built from tables:"
319
399
  relations.select { |_k, v| !v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
320
- puts "Classes built from views:"
400
+ puts "Classes that can be built from views:"
321
401
  relations.select { |_k, v| v.key?(:isView) }.keys.each { |k| puts ActiveSupport::Inflector.singularize(k).camelize }
322
402
  # pp relations; nil
323
403
 
@@ -5,67 +5,219 @@ 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"
8
9
  config.brick = ActiveSupport::OrderedOptions.new
9
- initializer 'brick.initialisation' do |app|
10
- Brick.enable_models = app.config.brick.fetch(:enable_models, true)
11
- Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
10
+ # initializer 'brick.initialisation' do |app|
11
+ ActiveSupport.on_load(:before_initialize) do |app|
12
+ puts "BEFORE - engine initialisation"
13
+ ::Brick.enable_models = app.config.brick.fetch(:enable_models, true)
14
+ ::Brick.enable_controllers = app.config.brick.fetch(:enable_controllers, true)
15
+ ::Brick.enable_views = app.config.brick.fetch(:enable_views, true)
16
+ ::Brick.enable_routes = app.config.brick.fetch(:enable_routes, true)
17
+ ::Brick.skip_database_views = app.config.brick.fetch(:skip_database_views, false)
12
18
 
13
- # ====================================
14
- # Dynamically create generic templates
15
- # ====================================
16
- if (Brick.enable_views = app.config.brick.fetch(:enable_views, true))
17
- ActionView::LookupContext.class_exec do
18
- alias :_brick_template_exists? :template_exists?
19
- def template_exists?(*args, **options)
20
- unless (is_template_exists = _brick_template_exists?(*args, **options))
21
- # Need to return true if we can fill in the blanks for a missing one
22
- # args will be something like: ["index", ["categories"]]
23
- model = args[1].map(&:camelize).join('::').singularize.constantize
24
- if (
25
- is_template_exists = model && (
26
- ['index', 'show'].include?(args.first) || # Everything has index and show
27
- # Only CRU stuff has create / update / destroy
28
- (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
19
+ # Specific database tables and views to omit when auto-creating models
20
+ ::Brick.exclude_tables = app.config.brick.fetch(:exclude_tables, [])
21
+
22
+ # Columns to treat as being metadata for purposes of identifying associative tables for has_many :through
23
+ ::Brick.metadata_columns = app.config.brick.fetch(:metadata_columns, ['created_at', 'updated_at', 'deleted_at'])
24
+
25
+ # Additional references (virtual foreign keys)
26
+ ::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)
27
+
28
+ # After we're initialized and before running the rest of stuff, put our configuration in place
29
+ ActiveSupport.on_load(:after_initialize) do |xyz|
30
+ puts "AFTER - engine initialisation"
31
+ # ====================================
32
+ # Dynamically create generic templates
33
+ # ====================================
34
+ if ::Brick.enable_views?
35
+ ActionView::LookupContext.class_exec do
36
+ alias :_brick_template_exists? :template_exists?
37
+ def template_exists?(*args, **options)
38
+ unless (is_template_exists = _brick_template_exists?(*args, **options))
39
+ # Need to return true if we can fill in the blanks for a missing one
40
+ # args will be something like: ["index", ["categories"]]
41
+ model = args[1].map(&:camelize).join('::').singularize.constantize
42
+ if (
43
+ is_template_exists = model && (
44
+ ['index', 'show'].include?(args.first) || # Everything has index and show
45
+ # Only CRU stuff has create / update / destroy
46
+ (!model.is_view? && ['new', 'create', 'edit', 'update', 'destroy'].include?(args.first))
47
+ )
29
48
  )
30
- )
31
- instance_variable_set(:@_brick_model, model)
49
+ instance_variable_set(:@_brick_model, model)
50
+ end
51
+ end
52
+ is_template_exists
53
+ end
54
+
55
+ alias :_brick_find_template :find_template
56
+ def find_template(*args, **options)
57
+ if @_brick_model
58
+ model_name = @_brick_model.name
59
+ pk = @_brick_model.primary_key
60
+ obj_name = model_name.underscore
61
+ table_name = model_name.pluralize.underscore
62
+ # This gets has_many as well as has_many :through
63
+ # %%% weed out ones that don't have an available model to reference
64
+ bts, hms = @_brick_model.reflect_on_all_associations.each_with_object([{}, {}]) do |a, s|
65
+ case a.macro
66
+ when :belongs_to
67
+ # Build #brick_descrip if needed
68
+ unless a.klass.instance_methods(false).include?(:brick_descrip)
69
+ descrip_col = (a.klass.columns.map(&:name) - a.klass._brick_get_fks -
70
+ (::Brick.config.metadata_columns || []) -
71
+ [a.klass.primary_key]).first&.to_sym
72
+ if descrip_col
73
+ a.klass.define_method :brick_descrip do
74
+ send(descrip_col)
75
+ end
76
+ end
77
+ end
78
+
79
+ s.first[a.foreign_key] = [a.name, a.klass]
80
+ when :has_many
81
+ s.last[a.name] = a
82
+ end
83
+ s
84
+ end
85
+ # Weed out has_manys that go to an associative table
86
+ associatives = hms.select { |k, v| v.options[:through] }.each_with_object({}) do |hmt, s|
87
+ s[hmt.first] = hms.delete(hmt.last.options[:through]) # End up with a hash of HMT names pointing to join-table associations
88
+ end
89
+ hms_headers = hms.each_with_object(+'') { |hm, s| s << "<th>HM#{'T' if hm.last.options[:through]} #{hm.first}</th>\n" }
90
+ hms_columns = hms.each_with_object(+'') do |hm, s|
91
+ hm_fk_name = if hm.last.options[:through]
92
+ associative = associatives[hm.last.name]
93
+ "'#{associative.name}.#{associative.foreign_key}'"
94
+ else
95
+ hm.last.foreign_key
96
+ end
97
+ s << "<td>
98
+ <%= link_to \"#\{#{obj_name}.#{hm.first}.count\} #{hm.first}\", #{hm.last.klass.name.underscore.pluralize}_path({ #{hm_fk_name}: #{obj_name}.#{pk} }) %>
99
+ </td>\n"
100
+ end
101
+
102
+ inline = case args.first
103
+ when 'index'
104
+ "<p style=\"color: green\"><%= notice %></p>
105
+
106
+ <h1>#{model_name.pluralize}</h1>
107
+ <% if @_brick_params&.present? %><h3>where <%= @_brick_params.each_with_object([]) { |v, s| s << \"#\{v.first\} = #\{v.last.inspect\}\" }.join(', ') %></h3><% end %>
108
+
109
+ <table id=\"#{table_name}\">
110
+ <tr>
111
+ <% is_first = true; is_need_id_col = nil
112
+ bts = { #{bts.each_with_object([]) { |v, s| s << "#{v.first.inspect} => [#{v.last.first.inspect}, #{v.last.last.name}, #{v.last.last.primary_key.inspect}]"}.join(', ')} }
113
+ @#{table_name}.columns.map(&:name).each do |col| %>
114
+ <% next if col == '#{pk}' || ::Brick.config.metadata_columns.include?(col) %>
115
+ <th>
116
+ <% if bt = bts[col]
117
+ if is_first
118
+ is_first = false
119
+ is_need_id_col = true %>
120
+ </th><th>
121
+ <% end %>
122
+ BT <%= \"#\{bt.first\}-\" unless bt[1].name.underscore == bt.first.to_s %><%= bt[1].name %>
123
+ <% else
124
+ is_first = false %>
125
+ <%= col %>
126
+ <% end %>
127
+ </th>
128
+ <% end %>
129
+ <% if is_first # STILL haven't been able to write a first non-key / non-metadata column?
130
+ is_first = false
131
+ is_need_id_col = true %>
132
+ <th></th>
133
+ <% end %>
134
+ #{hms_headers}
135
+ </tr>
136
+
137
+ <% @#{table_name}.each do |#{obj_name}| %>
138
+ <tr>
139
+ <% is_first = true
140
+ if is_need_id_col
141
+ is_first = false %>
142
+ <td><%= link_to \"#\{#{obj_name}.class.name\} ##\{#{obj_name}.id\}\", #{obj_name} %></td>
143
+ <% end %>
144
+ <% #{obj_name}.attributes.each do |k, val| %>
145
+ <% next if k == '#{pk}' || ::Brick.config.metadata_columns.include?(k) %>
146
+ <td>
147
+ <% if (bt = bts[k]) %>
148
+ <%= obj = bt[1].find_by(bt.last => val); link_to obj.brick_descrip, obj %>
149
+ <% elsif is_first %>
150
+ <%= is_first = false; link_to val, #{obj_name} %>
151
+ <% else %>
152
+ <%= val %>
153
+ <% end %>
154
+ </td>
155
+ <% end %>
156
+ #{hms_columns}
157
+ <!-- td>X</td -->
158
+ </tr>
159
+ <% end %>
160
+ </table>
161
+
162
+ #{"<hr><%= link_to \"New #{obj_name}\", new_#{obj_name}_path %>" unless @_brick_model.is_view?}
163
+ "
164
+ when 'show'
165
+ "<%= @#{@_brick_model.name.underscore}.inspect %>"
166
+ end
167
+ # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
168
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
169
+ handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
170
+ ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
171
+ else
172
+ _brick_find_template(*args, **options)
32
173
  end
33
174
  end
34
- is_template_exists
35
175
  end
176
+ end
36
177
 
37
- alias :_brick_find_template :find_template
38
- def find_template(*args, **options)
39
- if @_brick_model
40
- inline = case args.first
41
- when 'index'
42
- # Something like: <%= @categories.inspect %>
43
- "<%= @#{@_brick_model.name.underscore.pluralize}.inspect %>"
44
- when 'show'
45
- "<%= @#{@_brick_model.name.underscore}.inspect %>"
178
+ if ::Brick.enable_routes?
179
+ ActionDispatch::Routing::RouteSet.class_exec do
180
+ alias _brick_finalize_routeset! finalize!
181
+ def finalize!(*args, **options)
182
+ unless @finalized
183
+ existing_controllers = routes.each_with_object({}) { |r, s| c = r.defaults[:controller]; s[c] = nil if c }
184
+ ::Rails.application.routes.append do
185
+ # %%% TODO: If no auto-controllers then enumerate the controllers folder in order to build matching routes
186
+ # If auto-controllers and auto-models are both enabled then this makes sense:
187
+ relations = (::Brick.instance_variable_get(:@relations) || {})[ActiveRecord::Base.connection_pool.object_id] || {}
188
+ relations.each do |k, v|
189
+ unless existing_controllers.key?(controller_name = k.underscore.pluralize)
190
+ options = {}
191
+ options[:only] = [:index, :show] if v.key?(:isView)
192
+ send(:resources, controller_name.to_sym, **options)
193
+ end
194
+ end
195
+ end
46
196
  end
47
- # As if it were an inline template (see #determine_template in actionview-5.2.6.2/lib/action_view/renderer/template_renderer.rb)
48
- keys = options.has_key?(:locals) ? options[:locals].keys : []
49
- handler = ActionView::Template.handler_for_extension(options[:type] || 'erb')
50
- ActionView::Template.new(inline, "auto-generated #{args.first} template", handler, locals: keys)
51
- else
52
- _brick_find_template(*args, **options)
197
+ _brick_finalize_routeset!(*args, **options)
53
198
  end
54
199
  end
55
200
  end
56
- end
57
201
 
58
- # Auto-routing behaviour
59
- if (::Brick.enable_routes = app.config.brick.fetch(:enable_routes, true))
60
- ::Brick.append_routes
61
- end
62
- # Additional references (virtual foreign keys)
63
- if (ars = (::Brick.additional_references = app.config.brick.fetch(:additional_references, nil)))
64
- ars = ars.call if ars.is_a?(Proc)
65
- ars = ars.to_a unless ars.is_a?(Array)
66
- ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
67
- ars.each do |fk|
68
- ::Brick._add_bt_and_hm(fk[0..2])
202
+ # Additional references (virtual foreign keys)
203
+ if (ars = ::Brick.config.additional_references)
204
+ ars = ars.call if ars.is_a?(Proc)
205
+ ars = ars.to_a unless ars.is_a?(Array)
206
+ ars = [ars] unless ars.empty? || ars.first.is_a?(Array)
207
+ ars.each do |fk|
208
+ ::Brick._add_bt_and_hm(fk[0..2])
209
+ end
210
+ end
211
+
212
+ # Find associative tables that can be set up for has_many :through
213
+ ::Brick.relations.each do |_key, tbl|
214
+ tbl_cols = tbl[:cols].keys
215
+ fks = tbl[:fks].each_with_object({}) { |fk, s| s[fk.last[:fk]] = fk.last[:inverse_table] if fk.last[:is_bt]; s }
216
+ # Aside from the primary key and the metadata columns created_at, updated_at, and deleted_at, if this table only has
217
+ # foreign keys then it can act as an associative table and thus be used with has_many :through.
218
+ if fks.length > 1 && (tbl_cols - fks.keys - (::Brick.config.metadata_columns || []) - tbl[:pkey].values.first).length.zero?
219
+ fks.each { |fk| tbl[:hmt_fks][fk.first] = fk.last }
220
+ end
69
221
  end
70
222
  end
71
223
  end
@@ -5,7 +5,7 @@ module Brick
5
5
  module VERSION
6
6
  MAJOR = 1
7
7
  MINOR = 0
8
- TINY = 1
8
+ TINY = 4
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
@@ -76,20 +76,12 @@ end
76
76
  # is first established), and then automatically creates models, controllers, views,
77
77
  # and routes based on those available relations.
78
78
  require 'brick/config'
79
- require 'brick/frameworks/rails'
79
+ if Gem::Specification.all_names.any? { |g| g.start_with?('rails-') }
80
+ require 'rails'
81
+ require 'brick/frameworks/rails'
82
+ end
80
83
  module Brick
81
84
  class << self
82
- def append_routes
83
- ::Rails.application.routes.append do
84
- relations = (::Brick.instance_variable_get(:@relations) || {})[ActiveRecord::Base.connection_pool.object_id] || {}
85
- relations.each do |k, v|
86
- options = {}
87
- options[:only] = [:index, :show] if v.key?(:isView)
88
- send(:resources, k.underscore.pluralize.to_sym, **options)
89
- end
90
- end
91
- end
92
-
93
85
  # All tables and views (what Postgres calls "relations" including column and foreign key info)
94
86
  def relations
95
87
  connections = Brick.instance_variable_get(:@relations) ||
@@ -150,6 +142,21 @@ module Brick
150
142
  !!Brick.config.enable_routes
151
143
  end
152
144
 
145
+ # @api public
146
+ def skip_database_views=(value)
147
+ Brick.config.skip_database_views = value
148
+ end
149
+
150
+ # @api public
151
+ def exclude_tables=(value)
152
+ Brick.config.exclude_tables = value
153
+ end
154
+
155
+ # @api public
156
+ def metadata_columns=(value)
157
+ Brick.config.metadata_columns = value
158
+ end
159
+
153
160
  # Additional table associations to use (Think of these as virtual foreign keys perhaps)
154
161
  # @api public
155
162
  def additional_references=(value)
@@ -193,10 +200,7 @@ module Brick
193
200
  end
194
201
  end
195
202
 
196
- require 'brick/extensions'
197
203
  require 'brick/version_number'
198
- # require 'brick/serializers/json'
199
- # require 'brick/serializers/yaml'
200
204
 
201
205
  require 'active_record'
202
206
  # Major compatibility fixes for ActiveRecord < 4.2
@@ -422,18 +426,18 @@ ActiveSupport.on_load(:active_record) do
422
426
  end
423
427
  end
424
428
 
425
- include ::Brick::Extensions
426
-
427
- unless ::Brick::Extensions::IS_AMOEBA
428
- # Add amoeba-compatible support
429
- module ActiveRecord
430
- class Base
431
- def self.amoeba(*args)
432
- puts "Amoeba called from #{name} with #{args.inspect}"
433
- end
434
- end
435
- end
436
- end
429
+ # include ::Brick::Extensions
430
+
431
+ # unless ::Brick::Extensions::IS_AMOEBA
432
+ # # Add amoeba-compatible support
433
+ # module ActiveRecord
434
+ # class Base
435
+ # def self.amoeba(*args)
436
+ # puts "Amoeba called from #{name} with #{args.inspect}"
437
+ # end
438
+ # end
439
+ # end
440
+ # end
437
441
  end
438
442
 
439
443
  # Do this earlier because stuff here gets mixed into JoinDependency::JoinAssociation and AssociationScope
@@ -522,3 +526,5 @@ if ActiveRecord.version < ::Gem::Version.new('5.2')
522
526
  end # module ActiveRecord
523
527
  # rubocop:enable Style/CommentedKeyword
524
528
  end
529
+
530
+ require 'brick/extensions'
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/generators'
4
- require 'rails/generators/active_record'
4
+ # require 'rails/generators/active_record'
5
5
 
6
6
  module Brick
7
- # Auto-generates an IMPORT_TEMPLATE entry for one or more models
8
7
  class InstallGenerator < ::Rails::Generators::Base
9
- include ::Rails::Generators::Migration
8
+ # include ::Rails::Generators::Migration
10
9
 
11
10
  source_root File.expand_path('templates', __dir__)
12
11
  class_option(
@@ -16,81 +15,128 @@ module Brick
16
15
  desc: 'Store changeset (diff) with each version'
17
16
  )
18
17
 
19
- desc 'Generates (but does not run) a migration to add a versions table.' \
20
- ' Also generates an initializer file for configuring Brick'
18
+ desc 'Generates an initializer file for configuring Brick'
21
19
 
22
- def create_migration_file
23
- add_brick_migration('create_versions')
24
- add_brick_migration('add_object_changes_to_versions') if options.with_changes?
25
- end
20
+ def create_initializer_file
21
+ unless File.exists?(filename = 'config/initializers/brick.rb')
22
+ create_file filename, "# frozen_string_literal: true
26
23
 
27
- def self.next_migration_number(dirname)
28
- ::ActiveRecord::Generators::Base.next_migration_number(dirname)
29
- end
24
+ # # Settings for the Brick gem
25
+ # # (By default this auto-creates models, controllers, views, and routes on-the-fly.)
30
26
 
31
- protected
27
+ # # Normally these all start out as being enabled, but can be selectively disabled:
28
+ # Brick.enable_routes = false
29
+ # Brick.enable_models = false
30
+ # Brick.enable_controllers = false
31
+ # Brick.enable_views = false
32
+
33
+ # # By default models are auto-created from database views, and set to be read-only. This can be skipped.
34
+ # Brick.skip_database_views = true
35
+
36
+ # # Any tables or views you'd like to skip when auto-creating models
37
+ # Brick.exclude_tables = ['custom_metadata', 'version_info']
32
38
 
33
- def add_brick_migration(template)
34
- migration_dir = File.expand_path('db/migrate')
35
- if self.class.migration_exists?(migration_dir, template)
36
- ::Kernel.warn "Migration already exists: #{template}"
37
- else
38
- migration_template(
39
- "#{template}.rb.erb",
40
- "db/migrate/#{template}.rb",
41
- item_type_options: item_type_options,
42
- migration_version: migration_version,
43
- versions_table_options: versions_table_options
44
- )
39
+ # # Additional table references which are used to create has_many / belongs_to associations inside auto-created
40
+ # # models. (You can consider these to be \"virtual foreign keys\" if you wish)... You only have to add these
41
+ # # in cases where your database for some reason does not have foreign key constraints defined. Sometimes for
42
+ # # performance reasons or just out of sheer laziness these might be missing.
43
+ # # Each of these virtual foreign keys is defined as an array having three values:
44
+ # # foreign table name / foreign key column / primary table name.
45
+ # # (We boldly expect that the primary key identified by ActiveRecord on the primary table will be accurate,
46
+ # # usually this is \"id\" but there are some good smarts that are used in case some other column has been set
47
+ # # to be the primary key.
48
+ # Brick.additional_references = [['orders', 'customer_id', 'customer'],
49
+ # ['customer', 'region_id', 'regions']]
50
+
51
+ # # We normally don't consider the timestamp columns \"created_at\", \"updated_at\", and \"deleted_at\" to count when
52
+ # # finding tables which can serve as associative tables in an N:M association. That is, ones that can be a
53
+ # # part of a has_many :through association. If you want to use different exclusion columns than our defaults
54
+ # # then this setting resets that list. For instance, here is the override for the Sakila sample database:
55
+ # Brick.metadata_columns = ['last_updated']
56
+
57
+ # # If a default route is not supplied, Brick attempts to find the most \"central\" table and wires up the default
58
+ # # route to go to the :index action for what would be a controller for that table. You can specify any controller
59
+ # # name and action you wish in order to override this and have that be the default route when none other has been
60
+ # # specified in routes.rb or elsewhere. (Or just use an empty string in order to disable this behaviour.)
61
+ # Brick.default_route_fallback = 'customers' # This defaults to \"customers/index\"
62
+ # Brick.default_route_fallback = 'orders/outstanding' # Example of a non-RESTful route
63
+ # Brick.default_route_fallback = '' # Omits setting a default route in the absence of any other
64
+ "
45
65
  end
46
66
  end
47
67
 
68
+ # def create_migration_file
69
+ # add_brick_migration('create_versions')
70
+ # add_brick_migration('add_object_changes_to_versions') if options.with_changes?
71
+ # end
72
+
73
+ # def self.next_migration_number(dirname)
74
+ # ::ActiveRecord::Generators::Base.next_migration_number(dirname)
75
+ # end
76
+
77
+ protected
78
+
79
+ # def add_brick_migration(template)
80
+ # migration_dir = File.expand_path('db/migrate')
81
+ # if self.class.migration_exists?(migration_dir, template)
82
+ # ::Kernel.warn "Migration already exists: #{template}"
83
+ # else
84
+ # migration_template(
85
+ # "#{template}.rb.erb",
86
+ # "db/migrate/#{template}.rb",
87
+ # item_type_options: item_type_options,
88
+ # migration_version: migration_version,
89
+ # versions_table_options: versions_table_options
90
+ # )
91
+ # end
92
+ # end
93
+
48
94
  private
49
95
 
50
- # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
51
- def item_type_options
52
- opt = { null: false }
53
- opt[:limit] = 191 if mysql?
54
- ", #{opt}"
55
- end
96
+ # # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
97
+ # def item_type_options
98
+ # opt = { null: false }
99
+ # opt[:limit] = 191 if mysql?
100
+ # ", #{opt}"
101
+ # end
56
102
 
57
- def migration_version
58
- return unless (major = ActiveRecord::VERSION::MAJOR) >= 5
103
+ # def migration_version
104
+ # return unless (major = ActiveRecord::VERSION::MAJOR) >= 5
59
105
 
60
- "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
61
- end
106
+ # "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
107
+ # end
62
108
 
63
- # Class names of MySQL adapters.
64
- # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
65
- # - `Mysql2Adapter` - Used by `mysql2` gem.
66
- def mysql?
67
- [
68
- 'ActiveRecord::ConnectionAdapters::MysqlAdapter',
69
- 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
70
- ].freeze.include?(ActiveRecord::Base.connection.class.name)
71
- end
109
+ # # Class names of MySQL adapters.
110
+ # # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
111
+ # # - `Mysql2Adapter` - Used by `mysql2` gem.
112
+ # def mysql?
113
+ # [
114
+ # 'ActiveRecord::ConnectionAdapters::MysqlAdapter',
115
+ # 'ActiveRecord::ConnectionAdapters::Mysql2Adapter'
116
+ # ].freeze.include?(ActiveRecord::Base.connection.class.name)
117
+ # end
72
118
 
73
- # Even modern versions of MySQL still use `latin1` as the default character
74
- # encoding. Many users are not aware of this, and run into trouble when they
75
- # try to use Brick in apps that otherwise tend to use UTF-8. Postgres, by
76
- # comparison, uses UTF-8 except in the unusual case where the OS is configured
77
- # with a custom locale.
78
- #
79
- # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
80
- # - http://www.postgresql.org/docs/9.4/static/multibyte.html
81
- #
82
- # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
83
- # to be fixed later by introducing a new charset, `utf8mb4`.
84
- #
85
- # - https://mathiasbynens.be/notes/mysql-utf8mb4
86
- # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
87
- #
88
- def versions_table_options
89
- if mysql?
90
- ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
91
- else
92
- ''
93
- end
94
- end
119
+ # # Even modern versions of MySQL still use `latin1` as the default character
120
+ # # encoding. Many users are not aware of this, and run into trouble when they
121
+ # # try to use Brick in apps that otherwise tend to use UTF-8. Postgres, by
122
+ # # comparison, uses UTF-8 except in the unusual case where the OS is configured
123
+ # # with a custom locale.
124
+ # #
125
+ # # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
126
+ # # - http://www.postgresql.org/docs/9.4/static/multibyte.html
127
+ # #
128
+ # # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
129
+ # # to be fixed later by introducing a new charset, `utf8mb4`.
130
+ # #
131
+ # # - https://mathiasbynens.be/notes/mysql-utf8mb4
132
+ # # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
133
+ # #
134
+ # def versions_table_options
135
+ # if mysql?
136
+ # ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
137
+ # else
138
+ # ''
139
+ # end
140
+ # end
95
141
  end
96
142
  end
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.1
4
+ version: 1.0.4
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-13 00:00:00.000000000 Z
11
+ date: 2022-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -156,6 +156,20 @@ dependencies:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
158
  version: 1.42.0
159
+ - !ruby/object:Gem::Dependency
160
+ name: mysql2
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.5'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '0.5'
159
173
  - !ruby/object:Gem::Dependency
160
174
  name: pg
161
175
  requirement: !ruby/object:Gem::Requirement