declare_schema 0.11.1 → 0.12.0.pre.1

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: 389da0d9f9e0753e62297aeca33c3c73305d95082a3f50baf6288362011e6f52
4
- data.tar.gz: 7d20d8ef4a085e37ec13273c020b8c3883e96359e9e4c7d173c3beccf5c73dab
3
+ metadata.gz: e1353d236bb050d93155e56eab2bf5e5d31da06338970b13278c07ed86ac3180
4
+ data.tar.gz: f70054b7f56fd18264abe2e719167a2d83ebaa143279dc784dfcaf16b4cb4514
5
5
  SHA512:
6
- metadata.gz: 0e4eb02bc1d989d648095b952e1feff00af9cbbc4ea3dbb7962f15fdbcafce2fac4bc93c467417d29396e6753993874c3c2faf882a620db0d54f3ea905195cf9
7
- data.tar.gz: d504a96bb5f60d7c64b03fd975c9e7c78fe9614f06a0aa73fffe0c0987786a554e3f603a79198f962b13b0b869690b839fb1cd0215196353064360ef81e46e51
6
+ metadata.gz: 7e541c39b62bf7228842f20830ae52d1b49dca66a6a834f38478cc3320aa65dea1c7b85becbcc75edf7382470e7bdf0bc9949a3665e72ff778a26aaae65ae15a
7
+ data.tar.gz: f3bdcaad7b1aa8341d168c5d10461737b54e7781dfadce2da26e289f2ca3192b6dd9775bdb8c99a040b17ba415be2725fcbd3451c518c96e901227f2572cf95c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
5
  Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.12.0] - Unreleased
8
+ ### Added
9
+ - `belongs_to` now always infers the `limit:` of the foreign key to match that of the primary key it points to.
10
+ Note: this isn't possible for polymorphic foreign keys, so it assumes `limit: 8` there...unless the schema
11
+ was migrated in the past with `limit: 4`.
12
+
7
13
  ## [0.11.1] - 2021-03-26
8
14
  ### Fixed
9
15
  - Fixed a bug where up and down in generated migration would be empty in Rails 4.
@@ -168,6 +174,7 @@ using the appropriate Rails configuration attributes.
168
174
  ### Added
169
175
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
170
176
 
177
+ [0.12.0]: https://github.com/Invoca/declare_schema/compare/v0.11.1...v0.12.0
171
178
  [0.11.1]: https://github.com/Invoca/declare_schema/compare/v0.11.0...v0.11.1
172
179
  [0.11.0]: https://github.com/Invoca/declare_schema/compare/v0.10.1...v0.11.0
173
180
  [0.10.1]: https://github.com/Invoca/declare_schema/compare/v0.10.0...v0.10.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.11.1)
4
+ declare_schema (0.12.0.pre.1)
5
5
  rails (>= 4.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -4,10 +4,10 @@ Declare your Rails/active_record model schemas and have database migrations gene
4
4
 
5
5
  ## Example
6
6
 
7
- Make a model and declare your schema within a `fields do ... end` block:
7
+ Make a model and declare your schema within a `declare_schema do ... end` block:
8
8
  ```ruby
9
9
  class Company < ActiveRecord::Base
10
- fields do
10
+ declare_schema do
11
11
  company_name :string, limit: 100
12
12
  ticker_symbol :string, limit: 4, null: true, index: true, unique: true
13
13
  employee_count :integer
@@ -187,7 +187,7 @@ at three separate levels
187
187
 
188
188
  ### Table Configuration
189
189
  In order to configure a table's default character set and collation, the `charset` and
190
- `collation` arguments can be added to the `fields` block.
190
+ `collation` arguments can be added to the `declare_schema` block.
191
191
 
192
192
  For example, if you have a comments model that needs `utf8mb4` support, it would look
193
193
  like the following:
@@ -197,7 +197,7 @@ like the following:
197
197
  # frozen_string_literal: true
198
198
 
199
199
  class Comment < ActiveRecord::Base
200
- fields charset: "utf8mb4", collation: "utf8mb4_bin" do
200
+ declare_schema charset: "utf8mb4", collation: "utf8mb4_bin" do
201
201
  subject :string, limit: 255
202
202
  content :text, limit: 0xffff_ffff
203
203
  end
@@ -217,7 +217,7 @@ look like the following:
217
217
  # frozen_string_literal: true
218
218
 
219
219
  class Comment < ActiveRecord::Base
220
- fields do
220
+ declare_schema do
221
221
  subject :string, limit: 255
222
222
  context :text, limit: 0xffff_ffff, charset: "utf8mb4", collation: "utf8mb4_bin"
223
223
  end
@@ -7,12 +7,14 @@ require 'declare_schema/field_declaration_dsl'
7
7
 
8
8
  module DeclareSchema
9
9
  module Macros
10
+ attr_reader :_table_options
11
+
10
12
  def fields(table_options = {}, &block)
11
13
  # Any model that calls 'fields' gets DeclareSchema::Model behavior
12
14
  DeclareSchema::Model.mix_in(self)
13
15
 
14
16
  @include_in_migration = true
15
- @table_options = table_options
17
+ @_table_options = table_options
16
18
 
17
19
  if block
18
20
  dsl = DeclareSchema::FieldDeclarationDsl.new(self)
@@ -31,7 +33,7 @@ module DeclareSchema
31
33
 
32
34
  # @include_in_migration = false #||= options.fetch(:include_in_migration, true); options.delete(:include_in_migration)
33
35
  @include_in_migration = true # TODO: Add back or delete the include_in_migration feature
34
- @table_options = table_options
36
+ @_table_options = table_options
35
37
 
36
38
  if block
37
39
  dsl = DeclareSchema::Dsl.new(self, null: false)
@@ -99,7 +99,10 @@ module DeclareSchema
99
99
  end
100
100
  end
101
101
 
102
- # Extend belongs_to so that it creates a FieldSpec for the foreign key
102
+ # Extend belongs_to so that it
103
+ # 1. creates a FieldSpec for the foreign key
104
+ # 2. declares an index on the foreign key
105
+ # 3. declares a foreign_key constraint
103
106
  def belongs_to(name, scope = nil, **options)
104
107
  column_options = {}
105
108
 
@@ -109,7 +112,10 @@ module DeclareSchema
109
112
  options[:optional] # infer :null from :optional
110
113
  end || false
111
114
  column_options[:default] = options.delete(:default) if options.has_key?(:default)
112
- column_options[:limit] = options.delete(:limit) if options.has_key?(:limit)
115
+ if options.has_key?(:limit)
116
+ options.delete(:limit)
117
+ ActiveSupport::Deprecation.warn("belongs_to limit: is deprecated since it is now inferred")
118
+ end
113
119
 
114
120
  index_options = {}
115
121
  index_options[:name] = options.delete(:index) if options.has_key?(:index)
@@ -136,7 +142,25 @@ module DeclareSchema
136
142
 
137
143
  refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
138
144
  fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
139
- declare_field(fkey.to_sym, :integer, column_options)
145
+ fkey_id_column_options = column_options.dup
146
+
147
+ # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
148
+ # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
149
+ # But we can't here, because that will mess up the autoloader to follow every belongs_to association right
150
+ # when it is declared. So instead we assume :bigint (integer limit: 8) below, while also registering this
151
+ # pre_migration: callback to double-check that assumption Just In Time--right before we generate a migration.
152
+ #
153
+ # The one downside of this approach is that application code that asks the field_spec for the declared
154
+ # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
155
+ # a limit: 4 primary key. It seems unlikely that any application code would do this.
156
+ fkey_id_column_options[:pre_migration] = ->(field_spec) do
157
+ if (inferred_limit = _infer_fk_limit(fkey, refl))
158
+ field_spec.sql_options[:limit] = inferred_limit
159
+ end
160
+ end
161
+
162
+ declare_field(fkey.to_sym, :bigint, fkey_id_column_options)
163
+
140
164
  if refl.options[:polymorphic]
141
165
  foreign_type = options[:foreign_type] || "#{name}_type"
142
166
  _declare_polymorphic_type_field(foreign_type, column_options)
@@ -147,6 +171,28 @@ module DeclareSchema
147
171
  end
148
172
  end
149
173
 
174
+ def _infer_fk_limit(fkey, refl)
175
+ if refl.options[:polymorphic]
176
+ if (fkey_column = columns_hash[fkey.to_s]) && fkey_column.type == :integer
177
+ fkey_column.limit
178
+ end
179
+ else
180
+ klass = refl.klass or raise "Couldn't find belongs_to klass for #{name} in #{refl.inspect}"
181
+ if (pk_id_type = klass._table_options&.[](:id))
182
+ if pk_id_type == :integer
183
+ 4
184
+ end
185
+ else
186
+ if klass.table_exists? && (pk_column = klass.columns_hash[klass._declared_primary_key])
187
+ pk_id_type = pk_column.type
188
+ if pk_id_type == :integer
189
+ pk_column.limit
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
150
196
  if ::ActiveSupport::VERSION::MAJOR < 5
151
197
  def primary_key
152
198
  super || 'id'
@@ -155,27 +201,27 @@ module DeclareSchema
155
201
 
156
202
  # returns the primary key (String) as declared with primary_key =
157
203
  # unlike the `primary_key` method, DOES NOT query the database to find the actual primary key in use right now
158
- # if no explicit primary key set, returns the default_defined_primary_key
159
- def _defined_primary_key
204
+ # if no explicit primary key set, returns the _default_declared_primary_key
205
+ def _declared_primary_key
160
206
  if defined?(@primary_key)
161
207
  @primary_key&.to_s
162
- end || _default_defined_primary_key
208
+ end || _default_declared_primary_key
163
209
  end
164
210
 
165
211
  private
166
212
 
167
- # if this is a derived class, returns the base class's _defined_primary_key
213
+ # if this is a derived class, returns the base class's _declared_primary_key
168
214
  # otherwise, returns 'id'
169
- def _default_defined_primary_key
215
+ def _default_declared_primary_key
170
216
  if self == base_class
171
217
  'id'
172
218
  else
173
- base_class._defined_primary_key
219
+ base_class._declared_primary_key
174
220
  end
175
221
  end
176
222
 
177
223
  def _rails_default_primary_key
178
- ::DeclareSchema::Model::IndexDefinition.new(self, [_defined_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
224
+ ::DeclareSchema::Model::IndexDefinition.new(self, [_declared_primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
179
225
  end
180
226
 
181
227
  # Declares the "foo_type" field that accompanies the "foo_id"
@@ -59,7 +59,7 @@ module DeclareSchema
59
59
  # might be getting migrated to a new type. We should be using just type as below. -Colin
60
60
  column.type_cast_from_database(default_value)
61
61
  else
62
- cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type) or
62
+ cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type.to_s) or
63
63
  raise "cast_type not found for #{type}"
64
64
  cast_type.deserialize(default_value)
65
65
  end
@@ -47,9 +47,9 @@ module DeclareSchema
47
47
  end
48
48
 
49
49
  def initialize(model, name, type, position: 0, **options)
50
- _defined_primary_key = model._defined_primary_key
50
+ _declared_primary_key = model._declared_primary_key
51
51
 
52
- name.to_s == _defined_primary_key and raise ArgumentError, "you may not provide a field spec for the primary key #{name.inspect}"
52
+ name.to_s == _declared_primary_key and raise ArgumentError, "you may not provide a field spec for the primary key #{name.inspect}"
53
53
 
54
54
  @model = model
55
55
  @name = name.to_sym
@@ -99,8 +99,8 @@ module DeclareSchema
99
99
 
100
100
  if @type.in?([:text, :string])
101
101
  if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
102
- @options[:charset] ||= model.table_options[:charset] || ::DeclareSchema.default_charset
103
- @options[:collation] ||= model.table_options[:collation] || ::DeclareSchema.default_collation
102
+ @options[:charset] ||= model._table_options&.[](:charset) || ::DeclareSchema.default_charset
103
+ @options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
104
104
  else
105
105
  @options.delete(:charset)
106
106
  @options.delete(:collation)
@@ -29,7 +29,7 @@ module DeclareSchema
29
29
  @connection = connection
30
30
  end
31
31
 
32
- def table_options
32
+ def _table_options
33
33
  {}
34
34
  end
35
35
 
@@ -47,7 +47,7 @@ module DeclareSchema
47
47
  false # no single-column primary key in database
48
48
  end
49
49
 
50
- def _defined_primary_key
50
+ def _declared_primary_key
51
51
  false # no single-column primary key declared
52
52
  end
53
53
 
@@ -37,7 +37,7 @@ module DeclareSchema
37
37
  def for_model(model, old_table_name = nil)
38
38
  t = old_table_name || model.table_name
39
39
 
40
- primary_key_columns = Array(model.connection.primary_key(t)).presence || sqlite_compound_primary_key(model, t) or
40
+ primary_key_columns = Array(model.connection.primary_key(t)).presence || fallback_find_primary_key(model, t) or
41
41
  raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
42
42
 
43
43
  primary_key_found = false
@@ -67,7 +67,7 @@ module DeclareSchema
67
67
  private
68
68
 
69
69
  # This is the old approach which is still needed for MySQL in Rails 4 and SQLite
70
- def sqlite_compound_primary_key(model, table)
70
+ def fallback_find_primary_key(model, table)
71
71
  ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) || ActiveSupport::VERSION::MAJOR < 5 or return nil
72
72
 
73
73
  connection = model.connection.dup
@@ -83,9 +83,11 @@ module DeclareSchema
83
83
  end
84
84
  end
85
85
 
86
- pk_index = connection.indexes(table).find { |index| index.name.to_s == PRIMARY_KEY_NAME } or return nil
87
-
88
- Array(pk_index.columns)
86
+ if (pk_index = connection.indexes(table).find { |index| index.name.to_s == PRIMARY_KEY_NAME })
87
+ Array(pk_index.columns)
88
+ elsif model.connection.columns(table).any? { |col| col.name == 'id' }
89
+ ['id']
90
+ end
89
91
  end
90
92
  end
91
93
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.11.1"
4
+ VERSION = "0.12.0.pre.1"
5
5
  end
@@ -187,6 +187,12 @@ module Generators
187
187
  models, db_tables = models_and_tables
188
188
  models_by_table_name = {}
189
189
  models.each do |m|
190
+ m.try(:field_specs)&.each do |_name, field_spec|
191
+ if (pre_migration = field_spec.options.delete(:pre_migration))
192
+ pre_migration.call(field_spec)
193
+ end
194
+ end
195
+
190
196
  if !models_by_table_name.has_key?(m.table_name)
191
197
  models_by_table_name[m.table_name] = m
192
198
  elsif m.superclass == models_by_table_name[m.table_name].superclass.superclass
@@ -203,8 +209,8 @@ module Generators
203
209
 
204
210
  to_create = model_table_names - db_tables
205
211
  to_drop = db_tables - model_table_names - self.class.always_ignore_tables
206
- to_change = model_table_names
207
212
  to_rename = extract_table_renames!(to_create, to_drop)
213
+ to_change = model_table_names
208
214
 
209
215
  renames = to_rename.map do |old_name, new_name|
210
216
  ::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
@@ -297,14 +303,14 @@ module Generators
297
303
  end
298
304
 
299
305
  def create_table_options(model, disable_auto_increment)
300
- primary_key = model._defined_primary_key
306
+ primary_key = model._declared_primary_key
301
307
  if primary_key.blank? || disable_auto_increment
302
308
  { id: false }
303
309
  elsif primary_key == "id"
304
310
  { id: :bigint }
305
311
  else
306
312
  { primary_key: primary_key.to_sym }
307
- end
313
+ end.merge(model._table_options)
308
314
  end
309
315
 
310
316
  def table_options_for_model(model)
@@ -312,8 +318,8 @@ module Generators
312
318
  {}
313
319
  else
314
320
  {
315
- charset: model.table_options[:charset] || ::DeclareSchema.default_charset,
316
- collation: model.table_options[:collation] || ::DeclareSchema.default_collation
321
+ charset: model._table_options&.[](:charset) || ::DeclareSchema.default_charset,
322
+ collation: model._table_options&.[](:collation) || ::DeclareSchema.default_collation
317
323
  }
318
324
  end
319
325
  end
@@ -336,18 +342,17 @@ module Generators
336
342
  new_table_name = model.table_name
337
343
 
338
344
  db_columns = model.connection.columns(current_table_name).index_by(&:name)
339
- key_missing = db_columns[model._defined_primary_key].nil? && model._defined_primary_key.present?
340
- if model._defined_primary_key.present?
341
- db_columns.delete(model._defined_primary_key)
345
+ if (pk = model._declared_primary_key.presence)
346
+ key_was_in_db_columns = db_columns.delete(pk)
342
347
  end
343
348
 
344
349
  model_column_names = model.field_specs.keys.map(&:to_s)
345
350
  db_column_names = db_columns.keys.map(&:to_s)
346
351
 
347
352
  to_add = model_column_names - db_column_names
348
- to_add += [model._defined_primary_key] if key_missing && model._defined_primary_key.present?
353
+ to_add += [pk] unless key_was_in_db_columns
349
354
  to_remove = db_column_names - model_column_names
350
- to_remove -= [model._defined_primary_key.to_sym] if model._defined_primary_key.present?
355
+ to_remove -= [pk.to_sym] if pk # TODO: The .to_sym here means this code is always a no-op, right? -Colin
351
356
 
352
357
  to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
353
358
 
@@ -41,10 +41,12 @@ RSpec.describe 'DeclareSchema API' do
41
41
 
42
42
  if ActiveSupport::VERSION::MAJOR == 5
43
43
  # TODO: get this to work on Travis for Rails 6
44
+ # TODO: uncomment since we're not on Travis any more? -Colin
44
45
  generate_migrations '-n', '-m'
45
46
  end
46
47
 
47
48
  require 'advert'
49
+ Advert.reset_primary_key
48
50
 
49
51
  ## The Basics
50
52
 
@@ -6,7 +6,7 @@ rescue LoadError
6
6
  end
7
7
 
8
8
  RSpec.describe DeclareSchema::Model::FieldSpec do
9
- let(:model) { double('model', table_options: {}, _defined_primary_key: 'id') }
9
+ let(:model) { double('model', _table_options: {}, _declared_primary_key: 'id') }
10
10
  let(:col_spec) { double('col_spec', type: :string) }
11
11
 
12
12
  before do
@@ -19,7 +19,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
19
19
  end
20
20
 
21
21
  generate_migrations '-n', '-m'
22
- expect(Foo._defined_primary_key).to eq('foo_id')
22
+ expect(Foo._declared_primary_key).to eq('foo_id')
23
23
 
24
24
  ### migrate from
25
25
  # rename from custom primary_key
@@ -31,7 +31,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
31
31
 
32
32
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
33
33
  generate_migrations '-n', '-m'
34
- expect(Foo._defined_primary_key).to eq('id')
34
+ expect(Foo._declared_primary_key).to eq('id')
35
35
 
36
36
  nuke_model_class(Foo)
37
37
 
@@ -72,7 +72,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
72
72
 
73
73
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
74
74
  generate_migrations '-n', '-m'
75
- expect(Foo._defined_primary_key).to eq('foo_id')
75
+ expect(Foo._declared_primary_key).to eq('foo_id')
76
76
  end
77
77
  end
78
78
  end
@@ -80,8 +80,7 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
80
80
  context 'Using declare_schema' do
81
81
  it "allows alternate primary keys" do
82
82
  class Foo < ActiveRecord::Base
83
- declare_schema do
84
- end
83
+ declare_schema { }
85
84
  self.primary_key = "foo_id"
86
85
  end
87
86
 
@@ -91,15 +90,14 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
91
90
  ### migrate from
92
91
  # rename from custom primary_key
93
92
  class Foo < ActiveRecord::Base
94
- declare_schema do
95
- end
93
+ declare_schema { }
96
94
  self.primary_key = "id"
97
95
  end
98
96
 
99
97
  puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
100
98
  allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
101
99
  generate_migrations '-n', '-m'
102
- expect(Foo.primary_key).to eq('id')
100
+ expect(Foo._declared_primary_key).to eq('id')
103
101
 
104
102
  nuke_model_class(Foo)
105
103
 
@@ -441,10 +441,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
441
441
  Advert.field_specs.delete(:category_id)
442
442
  Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
443
443
 
444
- ### Timestamps and Optimimistic Locking
444
+ ### Timestamps and Optimistic Locking
445
445
 
446
446
  # `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
447
- # Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
447
+ # Similarly, `lock_version` can be declared with the "shorthand" `optimistic_lock`.
448
448
 
449
449
  class Advert < ActiveRecord::Base
450
450
  fields do
@@ -2039,7 +2039,6 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2039
2039
  add_foreign_key :affiliates, :categories, column: :category_id, name: :index_affiliates_on_category_id
2040
2040
  EOS
2041
2041
  )
2042
- binding.pry
2043
2042
  migrate
2044
2043
 
2045
2044
  nuke_model_class(Advertiser)
@@ -2242,80 +2241,202 @@ RSpec.describe 'DeclareSchema Migration Generator' do
2242
2241
  let(:optional_flag) { { false => optional_false, true => optional_true } }
2243
2242
 
2244
2243
  describe 'belongs_to' do
2245
- before do
2246
- unless defined?(AdCategory)
2247
- class AdCategory < ActiveRecord::Base
2248
- declare_schema { }
2244
+ context 'with AdCategory and Advert in DB' do
2245
+ before do
2246
+ unless defined?(AdCategory)
2247
+ class AdCategory < ActiveRecord::Base
2248
+ declare_schema { }
2249
+ end
2249
2250
  end
2250
- end
2251
2251
 
2252
- class Advert < ActiveRecord::Base
2253
- declare_schema do
2254
- string :name, limit: 250, null: true
2255
- integer :category_id, limit: 8
2256
- integer :nullable_category_id, limit: 8, null: true
2252
+ class Advert < ActiveRecord::Base
2253
+ declare_schema do
2254
+ string :name, limit: 250, null: true
2255
+ integer :category_id, limit: 8
2256
+ integer :nullable_category_id, limit: 8, null: true
2257
+ end
2257
2258
  end
2259
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2260
+ ActiveRecord::Migration.class_eval(up)
2258
2261
  end
2259
- up = Generators::DeclareSchema::Migration::Migrator.run.first
2260
- ActiveRecord::Migration.class_eval(up)
2261
- end
2262
-
2263
- it 'passes through optional: when given' do
2264
- class AdvertBelongsTo < ActiveRecord::Base
2265
- self.table_name = 'adverts'
2266
- declare_schema { }
2267
- reset_column_information
2268
- belongs_to :ad_category, optional: true
2269
- end
2270
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2271
- end
2272
2262
 
2273
- describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
2274
- it 'passes through optional: true, null: false' do
2263
+ it 'passes through optional: when given' do
2275
2264
  class AdvertBelongsTo < ActiveRecord::Base
2276
2265
  self.table_name = 'adverts'
2277
2266
  declare_schema { }
2278
2267
  reset_column_information
2279
- belongs_to :ad_category, optional: true, null: false
2268
+ belongs_to :ad_category, optional: true
2280
2269
  end
2281
2270
  expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2282
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
2283
2271
  end
2284
2272
 
2285
- it 'passes through optional: false, null: true' do
2286
- class AdvertBelongsTo < ActiveRecord::Base
2287
- self.table_name = 'adverts'
2288
- declare_schema { }
2289
- reset_column_information
2290
- belongs_to :ad_category, optional: false, null: true
2273
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
2274
+ it 'passes through optional: true, null: false' do
2275
+ class AdvertBelongsTo < ActiveRecord::Base
2276
+ self.table_name = 'adverts'
2277
+ declare_schema { }
2278
+ reset_column_information
2279
+ belongs_to :ad_category, optional: true, null: false
2280
+ end
2281
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2282
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
2283
+ end
2284
+
2285
+ it 'passes through optional: false, null: true' do
2286
+ class AdvertBelongsTo < ActiveRecord::Base
2287
+ self.table_name = 'adverts'
2288
+ declare_schema { }
2289
+ reset_column_information
2290
+ belongs_to :ad_category, optional: false, null: true
2291
+ end
2292
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
2293
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
2294
+ end
2295
+ end
2296
+
2297
+ [false, true].each do |nullable|
2298
+ context "nullable=#{nullable}" do
2299
+ it 'infers optional: from null:' do
2300
+ eval <<~EOS
2301
+ class AdvertBelongsTo < ActiveRecord::Base
2302
+ declare_schema { }
2303
+ belongs_to :ad_category, null: #{nullable}
2304
+ end
2305
+ EOS
2306
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2307
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2308
+ end
2309
+
2310
+ it 'infers null: from optional:' do
2311
+ eval <<~EOS
2312
+ class AdvertBelongsTo < ActiveRecord::Base
2313
+ declare_schema { }
2314
+ belongs_to :ad_category, optional: #{nullable}
2315
+ end
2316
+ EOS
2317
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2318
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2319
+ end
2291
2320
  end
2292
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
2293
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
2321
+ end
2322
+
2323
+ it 'deprecates limit:' do
2324
+ expect(ActiveSupport::Deprecation).to receive(:warn).with("belongs_to limit: is deprecated since it is now inferred")
2325
+ eval <<~EOS
2326
+ class UsingLimit < ActiveRecord::Base
2327
+ declare_schema { }
2328
+ belongs_to :ad_category, limit: 4
2329
+ end
2330
+ EOS
2294
2331
  end
2295
2332
  end
2296
2333
 
2297
- [false, true].each do |nullable|
2298
- context "nullable=#{nullable}" do
2299
- it 'infers optional: from null:' do
2300
- eval <<~EOS
2301
- class AdvertBelongsTo < ActiveRecord::Base
2302
- declare_schema { }
2303
- belongs_to :ad_category, null: #{nullable}
2304
- end
2305
- EOS
2306
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2307
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2334
+ context 'when parent object PKs have different limits' do
2335
+ before do
2336
+ class IdDefault < ActiveRecord::Base
2337
+ declare_schema { }
2338
+ end
2339
+ class Id4 < ActiveRecord::Base
2340
+ declare_schema id: :integer do
2341
+ end
2342
+ end
2343
+ class Id8 < ActiveRecord::Base
2344
+ declare_schema id: :bigint do
2345
+ end
2346
+ end
2347
+ class Fk < ActiveRecord::Base
2348
+ declare_schema { }
2349
+ belongs_to :id_default, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2350
+ belongs_to :id4, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2351
+ belongs_to :id8, (ActiveSupport::VERSION::MAJOR < 5 ? { constraint: false } : {})
2352
+ end
2353
+ end
2354
+
2355
+ it 'creates the proper PKs' do
2356
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2357
+
2358
+ create_id4_defaults = up.split("\n").grep(/create_table :id_defaults/).first
2359
+ expect(create_id4_defaults).to be, up
2360
+ expect(create_id4_defaults).to match(/, id: :bigint/)
2361
+
2362
+ create_id4s = up.split("\n").grep(/create_table :id4s/).first
2363
+ expect(create_id4s).to be, up
2364
+ expect(create_id4s).to match(/, id: :integer/)
2365
+
2366
+ create_id8s = up.split("\n").grep(/create_table :id8s/).first
2367
+ expect(create_id8s).to be, up
2368
+ expect(create_id8s).to match(/, id: :bigint/)
2369
+ end
2370
+
2371
+ it 'infers the correct FK type from the create_table id: type' do
2372
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2373
+
2374
+ create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
2375
+ if defined?(SQLite3)
2376
+ create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
2377
+ expect(create_fks).to eq([
2378
+ 't.integer :id_default_id, limit: X',
2379
+ 't.integer :id4_id, limit: X',
2380
+ 't.integer :id8_id, limit: X'
2381
+ ]), up
2382
+ else
2383
+ expect(create_fks).to eq([
2384
+ 't.integer :id_default_id, limit: 8',
2385
+ 't.integer :id4_id, limit: 4',
2386
+ 't.integer :id8_id, limit: 8'
2387
+ ]), up
2308
2388
  end
2389
+ end
2309
2390
 
2310
- it 'infers null: from optional:' do
2311
- eval <<~EOS
2312
- class AdvertBelongsTo < ActiveRecord::Base
2313
- declare_schema { }
2314
- belongs_to :ad_category, optional: #{nullable}
2315
- end
2316
- EOS
2317
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2318
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2391
+ context "when parent objects were migrated before and later definitions don't have explicit id:" do
2392
+ before do
2393
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2394
+ ActiveRecord::Migration.class_eval up
2395
+ nuke_model_class(IdDefault)
2396
+ nuke_model_class(Id4)
2397
+ nuke_model_class(Id8)
2398
+ nuke_model_class(Fk)
2399
+ ActiveRecord::Base.connection.schema_cache.clear!
2400
+
2401
+
2402
+ class NewIdDefault < ActiveRecord::Base
2403
+ self.table_name = 'id_defaults'
2404
+ declare_schema { }
2405
+ end
2406
+ class NewId4 < ActiveRecord::Base
2407
+ self.table_name = 'id4s'
2408
+ declare_schema { }
2409
+ end
2410
+ class NewId8 < ActiveRecord::Base
2411
+ self.table_name = 'id8s'
2412
+ declare_schema { }
2413
+ end
2414
+ class NewFk < ActiveRecord::Base
2415
+ declare_schema { }
2416
+ belongs_to :new_id_default
2417
+ belongs_to :new_id4
2418
+ belongs_to :new_id8
2419
+ end
2420
+ end
2421
+
2422
+ it 'infers the correct FK :integer limit: ' do
2423
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2424
+
2425
+ create_fks = up.split("\n").grep(/t\.integer /).map { |command| command.gsub(', null: false', '').gsub(/^ +/, '') }
2426
+ if defined?(SQLite3)
2427
+ create_fks.map! { |command| command.gsub(/limit: [a-z0-9]+/, 'limit: X') }
2428
+ expect(create_fks).to eq([
2429
+ 't.integer :new_id_default_id, limit: X',
2430
+ 't.integer :new_id4_id, limit: X',
2431
+ 't.integer :new_id8_id, limit: X'
2432
+ ]), up
2433
+ else
2434
+ expect(create_fks).to eq([
2435
+ 't.integer :new_id_default_id, limit: 8',
2436
+ 't.integer :new_id4_id, limit: 4',
2437
+ 't.integer :new_id8_id, limit: 8'
2438
+ ]), up
2439
+ end
2319
2440
  end
2320
2441
  end
2321
2442
  end
@@ -55,7 +55,7 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
55
55
 
56
56
  describe '#table_options' do
57
57
  it 'returns empty hash' do
58
- expect(subject.table_options).to eq({})
58
+ expect(subject._table_options).to eq({})
59
59
  end
60
60
  end
61
61
 
@@ -86,13 +86,13 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
86
86
 
87
87
  describe '#primary_key' do
88
88
  it 'returns false' do
89
- expect(subject._defined_primary_key).to eq(false)
89
+ expect(subject._declared_primary_key).to eq(false)
90
90
  end
91
91
  end
92
92
 
93
- describe '#_defined_primary_key' do
93
+ describe '#_declared_primary_key' do
94
94
  it 'returns false' do
95
- expect(subject._defined_primary_key).to eq(false)
95
+ expect(subject._declared_primary_key).to eq(false)
96
96
  end
97
97
  end
98
98
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declare_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.12.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca Development adapted from hobo_fields by Tom Locke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-26 00:00:00.000000000 Z
11
+ date: 2021-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails