declare_schema 0.9.0 → 0.10.0.pre.dc.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: 531940d48f1fe38830944576136ff76e2b29dc69a323a2a8e3f60f431731f104
4
- data.tar.gz: 3afe0ce91fa631f4fa07d10ef1039f788589d440e87245c859db9bbe0179972d
3
+ metadata.gz: ba8cc9ec92cef0b417f1c8a4f19926800f65d81d474bef96daed41073ad01f12
4
+ data.tar.gz: 78bbc7018dd9b253f1ec294cb00f63df8153e62f26028c2bfbc63934dac0872b
5
5
  SHA512:
6
- metadata.gz: 67270e128e00f45b8ed8393b85ffe65ab4bc7ae197296adacf22c37629bdf5e768d6e204987b67e2d63b3ac0cc79aadb0b02c2c4f60e7e4b85e01f4d24ef55e2
7
- data.tar.gz: c9763a6eacd9d1d1deef9e829072e974ea53a5575f0938a8b4202e4210aa704186022a12e8704c08edde24bc73c2da7f95b467b77f3c81e422df54eddaf2352a
6
+ metadata.gz: d6571074ee8f2961f176d91d3759fd82ac5acf7b8a263f5f2121441003ba7e172148ac50f5946926872b979384ace46cf191382c447f5807a688798813385d73
7
+ data.tar.gz: 52da509db48d6e858b905c03d553d2af57d1fe67ff540c66e8f9078ac76b4c21441fec99e68befb207151e36c987eda62d3e38b993bb57de5a748ee423456e6a
@@ -57,4 +57,4 @@ jobs:
57
57
  git config --global user.email "dummy@example.com"
58
58
  git config --global user.name "dummy"
59
59
  MYSQL_PORT=3306 bundle exec rake test:prepare_testapp[force]
60
- bundle exec rake test:all < test_responses.txt
60
+ bundle exec rake test:all
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.10.0] - Unreleased
8
+ ### Deprecated
9
+ - The `fields` dsl method is being deprecated.
10
+
11
+ ### Added
12
+ - Added the `declare_schema` method to replace `fields`. We now expect a column's type to come before the name
13
+ i.e. `declare schema { string :title }`. Otherwise, there is no difference between `fields` and `declare_schema`.
14
+
7
15
  ## [0.9.0] - 2021-03-01
8
16
  ### Added
9
17
  - Added configurable default settings for `default_text_limit`, `default_string_limit`, `default_null`,
@@ -140,6 +148,7 @@ using the appropriate Rails configuration attributes.
140
148
  ### Added
141
149
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
142
150
 
151
+ [0.10.0]: https://github.com/Invoca/declare_schema/compare/v0.9.0...v0.10.0
143
152
  [0.9.0]: https://github.com/Invoca/declare_schema/compare/v0.8.0...v0.9.0
144
153
  [0.8.0]: https://github.com/Invoca/declare_schema/compare/v0.7.1...v0.8.0
145
154
  [0.7.1]: https://github.com/Invoca/declare_schema/compare/v0.7.0...v0.7.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.9.0)
4
+ declare_schema (0.10.0.pre.dc.1)
5
5
  rails (>= 4.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -228,5 +228,5 @@ or add it to your `bundler` Gemfile:
228
228
  To run tests:
229
229
  ```
230
230
  rake test:prepare_testapp[force]
231
- rake test:all < test_responses.txt
231
+ rake test:all
232
232
  ```
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/proxy_object'
4
+
5
+ module DeclareSchema
6
+ class Dsl < BasicObject # avoid Object because that gets extended by lots of gems
7
+ include ::Kernel # but we need the basic class methods
8
+
9
+ instance_methods.each do |m|
10
+ unless m.to_s.starts_with?('__') || m.in?([:object_id, :instance_eval])
11
+ undef_method(m)
12
+ end
13
+ end
14
+
15
+ def initialize(model, options = {})
16
+ @model = model
17
+ @options = options
18
+ end
19
+
20
+ attr_reader :model
21
+
22
+ def timestamps
23
+ field(:created_at, :datetime, null: true)
24
+ field(:updated_at, :datetime, null: true)
25
+ end
26
+
27
+ def optimistic_lock
28
+ field(:lock_version, :integer, default: 1, null: false)
29
+ end
30
+
31
+ def field(name, type, *args)
32
+ options = args.extract_options!
33
+ @model.declare_field(name, type, *(args + [@options.merge(options)]))
34
+ end
35
+
36
+ def method_missing(type, name, *args)
37
+ field(name, type, *args)
38
+ end
39
+ end
40
+ end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_record'
4
+ require 'declare_schema/dsl'
4
5
  require 'declare_schema/model'
5
6
  require 'declare_schema/field_declaration_dsl'
6
7
 
7
8
  module DeclareSchema
8
- module FieldsDsl
9
+ module Macros
9
10
  def fields(table_options = {}, &block)
10
11
  # Any model that calls 'fields' gets DeclareSchema::Model behavior
11
12
  DeclareSchema::Model.mix_in(self)
12
13
 
13
- # @include_in_migration = false #||= options.fetch(:include_in_migration, true); options.delete(:include_in_migration)
14
14
  @include_in_migration = true
15
15
  @table_options = table_options
16
16
 
@@ -23,7 +23,26 @@ module DeclareSchema
23
23
  end
24
24
  end
25
25
  end
26
+ deprecate :fields, deprecator: ActiveSupport::Deprecation.new('1.0', 'DeclareSchema')
27
+
28
+ def declare_schema(table_options = {}, &block)
29
+ # Any model that calls 'fields' gets DeclareSchema::Model behavior
30
+ DeclareSchema::Model.mix_in(self)
31
+
32
+ # @include_in_migration = false #||= options.fetch(:include_in_migration, true); options.delete(:include_in_migration)
33
+ @include_in_migration = true # TODO: Add back or delete the include_in_migration feature
34
+ @table_options = table_options
35
+
36
+ if block
37
+ dsl = DeclareSchema::Dsl.new(self, null: false)
38
+ if block.arity == 1
39
+ yield dsl
40
+ else
41
+ dsl.instance_eval(&block)
42
+ end
43
+ end
44
+ end
26
45
  end
27
46
  end
28
47
 
29
- ActiveRecord::Base.singleton_class.prepend DeclareSchema::FieldsDsl
48
+ ActiveRecord::Base.singleton_class.prepend DeclareSchema::Macros
@@ -41,7 +41,7 @@ module DeclareSchema
41
41
  eval <<~EOS
42
42
  def self.inherited(klass)
43
43
  unless klass.field_specs.has_key?(inheritance_column)
44
- fields do |f|
44
+ declare_schema do |f|
45
45
  f.field(inheritance_column, :string, limit: 255, null: true)
46
46
  end
47
47
  index(inheritance_column)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0.pre.dc.1"
5
5
  end
@@ -69,11 +69,11 @@ module DeclareSchema
69
69
  def declare_model_fields_and_associations
70
70
  buffer = ::DeclareSchema::Support::IndentedBuffer.new(indent: 2)
71
71
  buffer.newline!
72
- buffer << 'fields do'
72
+ buffer << 'declare_schema do'
73
73
  buffer.indent! do
74
74
  field_attributes.each do |attribute|
75
- decl = "%-#{max_attribute_length}s" % attribute.name + ' ' +
76
- attribute.type.to_sym.inspect +
75
+ decl = "%-#{max_attribute_length}s" % attribute.type + ' ' +
76
+ attribute.name.to_sym.inspect +
77
77
  case attribute.type.to_s
78
78
  when 'string'
79
79
  ', limit: 255'
@@ -113,7 +113,7 @@ module DeclareSchema
113
113
  end
114
114
 
115
115
  def max_attribute_length
116
- attributes.map { |attribute| attribute.name.length }.max
116
+ attributes.map { |attribute| attribute.type.length }.max
117
117
  end
118
118
 
119
119
  def field_attributes
@@ -20,9 +20,9 @@ RSpec.describe 'DeclareSchema API' do
20
20
  expect_model_definition_to_eq('advert', <<~EOS)
21
21
  class Advert < #{active_record_base_class}
22
22
 
23
- fields do
24
- title :string, limit: 255
25
- body :text
23
+ declare_schema do
24
+ string :title, limit: 255
25
+ text :body
26
26
  end
27
27
 
28
28
  end
@@ -91,8 +91,8 @@ RSpec.describe 'DeclareSchema API' do
91
91
  class AdvertWithRequiredTitle < ActiveRecord::Base
92
92
  self.table_name = 'adverts'
93
93
 
94
- fields do
95
- title :string, :required, limit: 255
94
+ declare_schema do
95
+ string :title, :required, limit: 255
96
96
  end
97
97
  end
98
98
 
@@ -110,8 +110,8 @@ RSpec.describe 'DeclareSchema API' do
110
110
  class AdvertWithUniqueTitle < ActiveRecord::Base
111
111
  self.table_name = 'adverts'
112
112
 
113
- fields do
114
- title :string, :unique, limit: 255
113
+ declare_schema do
114
+ string :title, :unique, limit: 255
115
115
  end
116
116
  end
117
117
 
@@ -3,29 +3,55 @@
3
3
  require_relative '../../../lib/declare_schema/field_declaration_dsl'
4
4
 
5
5
  RSpec.describe DeclareSchema::FieldDeclarationDsl do
6
- before do
7
- load File.expand_path('prepare_testapp.rb', __dir__)
6
+ let(:model) { TestModel.new }
7
+ subject { declared_class.new(model) }
8
8
 
9
- class TestModel < ActiveRecord::Base
10
- fields do
11
- name :string, limit: 127
9
+ context 'Using fields' do
10
+ before do
11
+ load File.expand_path('prepare_testapp.rb', __dir__)
12
12
 
13
- timestamps
13
+ class TestModel < ActiveRecord::Base
14
+ fields do
15
+ name :string, limit: 127
16
+
17
+ timestamps
18
+ end
14
19
  end
15
20
  end
16
- end
17
21
 
18
- let(:model) { TestModel.new }
19
- subject { declared_class.new(model) }
22
+ it 'has fields' do
23
+ expect(TestModel.field_specs).to be_kind_of(Hash)
24
+ expect(TestModel.field_specs.keys).to eq(['name', 'created_at', 'updated_at'])
25
+ expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
26
+ end
20
27
 
21
- it 'has fields' do
22
- expect(TestModel.field_specs).to be_kind_of(Hash)
23
- expect(TestModel.field_specs.keys).to eq(['name', 'created_at', 'updated_at'])
24
- expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
28
+ it 'stores limits' do
29
+ expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
30
+ end
25
31
  end
26
32
 
27
- it 'stores limits' do
28
- expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
33
+ context 'Using declare_schema' do
34
+ before do
35
+ load File.expand_path('prepare_testapp.rb', __dir__)
36
+
37
+ class TestModel < ActiveRecord::Base
38
+ declare_schema do
39
+ string :name, limit: 127
40
+
41
+ timestamps
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'has fields' do
47
+ expect(TestModel.field_specs).to be_kind_of(Hash)
48
+ expect(TestModel.field_specs.keys).to eq(['name', 'created_at', 'updated_at'])
49
+ expect(TestModel.field_specs.values.map(&:type)).to eq([:string, :datetime, :datetime])
50
+ end
51
+
52
+ it 'stores limits' do
53
+ expect(TestModel.field_specs['name'].limit).to eq(127), TestModel.field_specs['name'].inspect
54
+ end
29
55
  end
30
56
 
31
57
  # TODO: fill out remaining tests
@@ -11,9 +11,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
11
11
  expect_model_definition_to_eq('alpha/beta', <<~EOS)
12
12
  class Alpha::Beta < #{active_record_base_class}
13
13
 
14
- fields do
15
- one :string, limit: 255
16
- two :integer
14
+ declare_schema do
15
+ string :one, limit: 255
16
+ integer :two
17
17
  end
18
18
 
19
19
  end
@@ -11,49 +11,101 @@ RSpec.describe 'DeclareSchema Migration Generator interactive primary key' do
11
11
  load File.expand_path('prepare_testapp.rb', __dir__)
12
12
  end
13
13
 
14
- it "allows alternate primary keys" do
15
- class Foo < ActiveRecord::Base
16
- fields do
14
+ context 'Using fields' do
15
+ it "allows alternate primary keys" do
16
+ class Foo < ActiveRecord::Base
17
+ fields do
18
+ end
19
+ self.primary_key = "foo_id"
17
20
  end
18
- self.primary_key = "foo_id"
19
- end
20
21
 
21
- generate_migrations '-n', '-m'
22
- expect(Foo._defined_primary_key).to eq('foo_id')
22
+ generate_migrations '-n', '-m'
23
+ expect(Foo._defined_primary_key).to eq('foo_id')
23
24
 
24
- ### migrate from
25
- # rename from custom primary_key
26
- class Foo < ActiveRecord::Base
27
- fields do
25
+ ### migrate from
26
+ # rename from custom primary_key
27
+ class Foo < ActiveRecord::Base
28
+ fields do
29
+ end
30
+ self.primary_key = "id"
28
31
  end
29
- self.primary_key = "id"
30
- end
31
32
 
32
- puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
33
- generate_migrations '-n', '-m'
34
- expect(Foo._defined_primary_key).to eq('id')
33
+ allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
34
+ generate_migrations '-n', '-m'
35
+ expect(Foo._defined_primary_key).to eq('id')
36
+
37
+ nuke_model_class(Foo)
38
+
39
+ ### migrate to
40
+
41
+ if Rails::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
42
+ # replace custom primary_key
43
+ class Foo < ActiveRecord::Base
44
+ fields do
45
+ end
46
+ self.primary_key = "foo_id"
47
+ end
48
+
49
+ allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
50
+ generate_migrations '-n', '-m'
51
+ expect(Foo._defined_primary_key).to eq('foo_id')
35
52
 
36
- nuke_model_class(Foo)
53
+ ### ensure it doesn't cause further migrations
37
54
 
38
- ### migrate to
55
+ # check no further migrations
56
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
57
+ expect(up).to eq("")
58
+ end
59
+ end
60
+ end
39
61
 
40
- if Rails::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
41
- # replace custom primary_key
62
+ context 'Using declare_schema' do
63
+ it "allows alternate primary keys" do
42
64
  class Foo < ActiveRecord::Base
43
- fields do
65
+ declare_schema do
44
66
  end
45
67
  self.primary_key = "foo_id"
46
68
  end
47
69
 
48
- puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
49
70
  generate_migrations '-n', '-m'
50
- expect(Foo._defined_primary_key).to eq('foo_id')
71
+ expect(Foo.primary_key).to eq('foo_id')
51
72
 
52
- ### ensure it doesn't cause further migrations
73
+ ### migrate from
74
+ # rename from custom primary_key
75
+ class Foo < ActiveRecord::Base
76
+ declare_schema do
77
+ end
78
+ self.primary_key = "id"
79
+ end
53
80
 
54
- # check no further migrations
55
- up = Generators::DeclareSchema::Migration::Migrator.run.first
56
- expect(up).to eq("")
81
+ puts "\n\e[45m Please enter 'id' (no quotes) at the next prompt \e[0m"
82
+ allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'id' }
83
+ generate_migrations '-n', '-m'
84
+ expect(Foo.primary_key).to eq('id')
85
+
86
+ nuke_model_class(Foo)
87
+
88
+ ### migrate to
89
+
90
+ if Rails::VERSION::MAJOR >= 5 && !defined?(Mysql2) # TODO TECH-4814 Put this test back for Mysql2
91
+ # replace custom primary_key
92
+ class Foo < ActiveRecord::Base
93
+ declare_schema do
94
+ end
95
+ self.primary_key = "foo_id"
96
+ end
97
+
98
+ puts "\n\e[45m Please enter 'drop id' (no quotes) at the next prompt \e[0m"
99
+ allow_any_instance_of(DeclareSchema::Support::ThorShell).to receive(:ask).with(/one of the rename choices or press enter to keep/) { 'drop id' }
100
+ generate_migrations '-n', '-m'
101
+ expect(Foo.primary_key).to eq('foo_id')
102
+
103
+ ### ensure it doesn't cause further migrations
104
+
105
+ # check no further migrations
106
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
107
+ expect(up).to eq("")
108
+ end
57
109
  end
58
110
  end
59
111
  end
@@ -55,1172 +55,2346 @@ RSpec.describe 'DeclareSchema Migration Generator' do
55
55
  end
56
56
  end
57
57
 
58
- # DeclareSchema - Migration Generator
59
- it 'generates migrations' do
60
- ## The migration generator -- introduction
58
+ context 'Using fields' do
59
+ # DeclareSchema - Migration Generator
60
+ it 'generates migrations' do
61
+ ## The migration generator -- introduction
61
62
 
62
- expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
63
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
63
64
 
64
- class Advert < ActiveRecord::Base
65
- end
65
+ class Advert < ActiveRecord::Base
66
+ end
66
67
 
67
- expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
68
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
68
69
 
69
- Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
70
+ Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
70
71
 
71
- Advert.connection.schema_cache.clear!
72
- Advert.reset_column_information
72
+ Advert.connection.schema_cache.clear!
73
+ Advert.reset_column_information
73
74
 
74
- class Advert < ActiveRecord::Base
75
- fields do
76
- name :string, limit: 250, null: true
75
+ class Advert < ActiveRecord::Base
76
+ fields do
77
+ name :string, limit: 250, null: true
78
+ end
77
79
  end
78
- end
79
80
 
80
- up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
81
- expect(migrations).to(
82
- migrate_up(<<~EOS.strip)
83
- create_table :adverts, id: :bigint do |t|
84
- t.string :name, limit: 250, null: true#{charset_and_collation}
85
- end#{charset_alter_table}
86
- EOS
87
- .and migrate_down("drop_table :adverts")
88
- )
89
- end
81
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
82
+ expect(migrations).to(
83
+ migrate_up(<<~EOS.strip)
84
+ create_table :adverts, id: :bigint do |t|
85
+ t.string :name, limit: 250, null: true#{charset_and_collation}
86
+ end#{charset_alter_table}
87
+ EOS
88
+ .and migrate_down("drop_table :adverts")
89
+ )
90
+ end
90
91
 
91
- ActiveRecord::Migration.class_eval(up)
92
- expect(Advert.columns.map(&:name)).to eq(["id", "name"])
92
+ ActiveRecord::Migration.class_eval(up)
93
+ expect(Advert.columns.map(&:name)).to eq(["id", "name"])
93
94
 
94
- if Rails::VERSION::MAJOR < 5
95
- # Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
96
- ActiveRecord::Base.connection.execute("drop table adverts")
97
- if defined?(Mysql2)
98
- ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTO_INCREMENT NOT NULL, name varchar(250)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
99
- else
100
- ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
95
+ if Rails::VERSION::MAJOR < 5
96
+ # Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
97
+ ActiveRecord::Base.connection.execute("drop table adverts")
98
+ if defined?(Mysql2)
99
+ ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTO_INCREMENT NOT NULL, name varchar(250)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
100
+ else
101
+ ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
102
+ end
101
103
  end
102
- end
103
104
 
104
- class Advert < ActiveRecord::Base
105
- fields do
106
- name :string, limit: 250, null: true
107
- body :text, null: true
108
- published_at :datetime, null: true
105
+ class Advert < ActiveRecord::Base
106
+ fields do
107
+ name :string, limit: 250, null: true
108
+ body :text, null: true
109
+ published_at :datetime, null: true
110
+ end
109
111
  end
110
- end
111
112
 
112
- Advert.connection.schema_cache.clear!
113
- Advert.reset_column_information
113
+ Advert.connection.schema_cache.clear!
114
+ Advert.reset_column_information
114
115
 
115
- expect(migrate).to(
116
- migrate_up(<<~EOS.strip)
117
- add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
118
- add_column :adverts, :published_at, :datetime, null: true
119
- EOS
120
- .and migrate_down(<<~EOS.strip)
121
- remove_column :adverts, :body
122
- remove_column :adverts, :published_at
123
- EOS
124
- )
116
+ expect(migrate).to(
117
+ migrate_up(<<~EOS.strip)
118
+ add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
119
+ add_column :adverts, :published_at, :datetime, null: true
120
+ EOS
121
+ .and migrate_down(<<~EOS.strip)
122
+ remove_column :adverts, :body
123
+ remove_column :adverts, :published_at
124
+ EOS
125
+ )
125
126
 
126
- Advert.field_specs.clear # not normally needed
127
- class Advert < ActiveRecord::Base
128
- fields do
129
- name :string, limit: 250, null: true
130
- body :text, null: true
127
+ Advert.field_specs.clear # not normally needed
128
+ class Advert < ActiveRecord::Base
129
+ fields do
130
+ name :string, limit: 250, null: true
131
+ body :text, null: true
132
+ end
131
133
  end
132
- end
133
134
 
134
- expect(migrate).to(
135
- migrate_up("remove_column :adverts, :published_at").and(
136
- migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
135
+ expect(migrate).to(
136
+ migrate_up("remove_column :adverts, :published_at").and(
137
+ migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
138
+ )
137
139
  )
138
- )
139
140
 
140
- nuke_model_class(Advert)
141
- class Advert < ActiveRecord::Base
142
- fields do
143
- title :string, limit: 250, null: true
144
- body :text, null: true
141
+ nuke_model_class(Advert)
142
+ class Advert < ActiveRecord::Base
143
+ fields do
144
+ title :string, limit: 250, null: true
145
+ body :text, null: true
146
+ end
145
147
  end
146
- end
147
148
 
148
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
149
- migrate_up(<<~EOS.strip)
150
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
151
- remove_column :adverts, :name
152
- EOS
153
- .and migrate_down(<<~EOS.strip)
154
- remove_column :adverts, :title
155
- add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
156
- EOS
157
- )
149
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
150
+ migrate_up(<<~EOS.strip)
151
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
152
+ remove_column :adverts, :name
153
+ EOS
154
+ .and migrate_down(<<~EOS.strip)
155
+ remove_column :adverts, :title
156
+ add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
157
+ EOS
158
+ )
158
159
 
159
- expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
160
- migrate_up("rename_column :adverts, :name, :title").and(
161
- migrate_down("rename_column :adverts, :title, :name")
160
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
161
+ migrate_up("rename_column :adverts, :name, :title").and(
162
+ migrate_down("rename_column :adverts, :title, :name")
163
+ )
162
164
  )
163
- )
164
165
 
165
- migrate
166
+ migrate
166
167
 
167
- class Advert < ActiveRecord::Base
168
- fields do
169
- title :text, null: true
170
- body :text, null: true
168
+ class Advert < ActiveRecord::Base
169
+ fields do
170
+ title :text, null: true
171
+ body :text, null: true
172
+ end
171
173
  end
172
- end
173
174
 
174
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
175
- migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
176
- migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
175
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
176
+ migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
177
+ migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
178
+ )
177
179
  )
178
- )
179
180
 
180
- class Advert < ActiveRecord::Base
181
- fields do
182
- title :string, default: "Untitled", limit: 250, null: true
183
- body :text, null: true
181
+ class Advert < ActiveRecord::Base
182
+ fields do
183
+ title :string, default: "Untitled", limit: 250, null: true
184
+ body :text, null: true
185
+ end
184
186
  end
185
- end
186
187
 
187
- expect(migrate).to(
188
- migrate_up(<<~EOS.strip)
189
- change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
190
- EOS
191
- .and migrate_down(<<~EOS.strip)
192
- change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
193
- EOS
194
- )
188
+ expect(migrate).to(
189
+ migrate_up(<<~EOS.strip)
190
+ change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
191
+ EOS
192
+ .and migrate_down(<<~EOS.strip)
193
+ change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
194
+ EOS
195
+ )
195
196
 
196
- ### Limits
197
+ ### Limits
197
198
 
198
- class Advert < ActiveRecord::Base
199
- fields do
200
- price :integer, null: true, limit: 2
199
+ class Advert < ActiveRecord::Base
200
+ fields do
201
+ price :integer, null: true, limit: 2
202
+ end
201
203
  end
202
- end
203
204
 
204
- up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
205
- expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
206
- end
207
-
208
- # Now run the migration, then change the limit:
209
-
210
- ActiveRecord::Migration.class_eval(up)
211
- class Advert < ActiveRecord::Base
212
- fields do
213
- price :integer, null: true, limit: 3
205
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
206
+ expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
214
207
  end
215
- end
216
208
 
217
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
218
- migrate_up(<<~EOS.strip)
219
- change_column :adverts, :price, :integer, limit: 3, null: true
220
- EOS
221
- .and migrate_down(<<~EOS.strip)
222
- change_column :adverts, :price, :integer, limit: 2, null: true
223
- EOS
224
- )
209
+ # Now run the migration, then change the limit:
225
210
 
226
- ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
227
- class Advert < ActiveRecord::Base
228
- fields do
229
- price :decimal, precision: 4, scale: 1, null: true
211
+ ActiveRecord::Migration.class_eval(up)
212
+ class Advert < ActiveRecord::Base
213
+ fields do
214
+ price :integer, null: true, limit: 3
215
+ end
230
216
  end
231
- end
232
217
 
233
- # Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
234
- # allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
235
- # If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
236
- # that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
237
-
238
- if defined?(SQLite3)
239
- expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
240
- end
218
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
219
+ migrate_up(<<~EOS.strip)
220
+ change_column :adverts, :price, :integer, limit: 3, null: true
221
+ EOS
222
+ .and migrate_down(<<~EOS.strip)
223
+ change_column :adverts, :price, :integer, limit: 2, null: true
224
+ EOS
225
+ )
241
226
 
242
- class Advert < ActiveRecord::Base
243
- fields do
244
- notes :text
245
- description :text, limit: 30000
227
+ ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
228
+ class Advert < ActiveRecord::Base
229
+ fields do
230
+ price :decimal, precision: 4, scale: 1, null: true
231
+ end
246
232
  end
247
- end
248
-
249
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
250
- migrate_up(<<~EOS.strip)
251
- add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
252
- add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
253
- add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
254
- EOS
255
- )
256
233
 
257
- Advert.field_specs.delete :price
258
- Advert.field_specs.delete :notes
259
- Advert.field_specs.delete :description
234
+ # Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
235
+ # allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
236
+ # If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
237
+ # that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
260
238
 
261
- # In MySQL, limits are applied, rounded up:
262
-
263
- if defined?(Mysql2)
264
- expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
239
+ if defined?(SQLite3)
240
+ expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
241
+ end
265
242
 
266
243
  class Advert < ActiveRecord::Base
267
244
  fields do
268
245
  notes :text
269
- description :text, limit: 250
246
+ description :text, limit: 30000
270
247
  end
271
248
  end
272
249
 
273
250
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
274
251
  migrate_up(<<~EOS.strip)
275
- add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
276
- add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
252
+ add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
253
+ add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
254
+ add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
277
255
  EOS
278
256
  )
279
257
 
258
+ Advert.field_specs.delete :price
280
259
  Advert.field_specs.delete :notes
260
+ Advert.field_specs.delete :description
261
+
262
+ # In MySQL, limits are applied, rounded up:
281
263
 
282
- # Limits that are too high for MySQL will raise an exception.
264
+ if defined?(Mysql2)
265
+ expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
283
266
 
284
- expect do
285
267
  class Advert < ActiveRecord::Base
286
268
  fields do
287
269
  notes :text
288
- description :text, limit: 0x1_0000_0000
270
+ description :text, limit: 250
289
271
  end
290
272
  end
291
- end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
292
273
 
293
- Advert.field_specs.delete :notes
274
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
275
+ migrate_up(<<~EOS.strip)
276
+ add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
277
+ add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
278
+ EOS
279
+ )
280
+
281
+ Advert.field_specs.delete :notes
282
+
283
+ # Limits that are too high for MySQL will raise an exception.
284
+
285
+ expect do
286
+ class Advert < ActiveRecord::Base
287
+ fields do
288
+ notes :text
289
+ description :text, limit: 0x1_0000_0000
290
+ end
291
+ end
292
+ end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
293
+
294
+ Advert.field_specs.delete :notes
295
+
296
+ # And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
297
+
298
+ # To start, we'll set the database schema for `description` to match the above limit of 250.
299
+
300
+ Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
301
+ Advert.connection.schema_cache.clear!
302
+ Advert.reset_column_information
303
+ expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
304
+ to eq(["adverts"])
305
+ expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
306
+
307
+ # Now migrate to an unstated text limit:
308
+
309
+ class Advert < ActiveRecord::Base
310
+ fields do
311
+ description :text
312
+ end
313
+ end
294
314
 
295
- # And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
315
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
316
+ migrate_up(<<~EOS.strip)
317
+ change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
318
+ EOS
319
+ .and migrate_down(<<~EOS.strip)
320
+ change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
321
+ EOS
322
+ )
296
323
 
297
- # To start, we'll set the database schema for `description` to match the above limit of 250.
324
+ # And migrate to a stated text limit that is the same as the unstated one:
298
325
 
299
- Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
326
+ class Advert < ActiveRecord::Base
327
+ fields do
328
+ description :text, limit: 0xffffffff
329
+ end
330
+ end
331
+
332
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
333
+ migrate_up(<<~EOS.strip)
334
+ change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
335
+ EOS
336
+ .and migrate_down(<<~EOS.strip)
337
+ change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
338
+ EOS
339
+ )
340
+ end
341
+
342
+ Advert.field_specs.clear
300
343
  Advert.connection.schema_cache.clear!
301
344
  Advert.reset_column_information
302
- expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
303
- to eq(["adverts"])
304
- expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
345
+ class Advert < ActiveRecord::Base
346
+ fields do
347
+ name :string, limit: 250, null: true
348
+ end
349
+ end
350
+
351
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
352
+ ActiveRecord::Migration.class_eval up
353
+
354
+ Advert.connection.schema_cache.clear!
355
+ Advert.reset_column_information
356
+
357
+ ### Foreign Keys
358
+
359
+ # DeclareSchema extends the `belongs_to` macro so that it also declares the
360
+ # foreign-key field. It also generates an index on the field.
361
+
362
+ class Category < ActiveRecord::Base; end
363
+ class Advert < ActiveRecord::Base
364
+ fields do
365
+ name :string, limit: 250, null: true
366
+ end
367
+ belongs_to :category
368
+ end
369
+
370
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
371
+ migrate_up(<<~EOS.strip)
372
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
373
+
374
+ add_index :adverts, [:category_id], name: 'on_category_id'
375
+
376
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" if defined?(Mysql2)}
377
+ EOS
378
+ .and migrate_down(<<~EOS.strip)
379
+ remove_column :adverts, :category_id
380
+
381
+ remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
382
+
383
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" if defined?(Mysql2)}
384
+ EOS
385
+ )
386
+
387
+ Advert.field_specs.delete(:category_id)
388
+ Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
389
+
390
+ # If you specify a custom foreign key, the migration generator observes that:
391
+
392
+ class Category < ActiveRecord::Base; end
393
+ class Advert < ActiveRecord::Base
394
+ fields { }
395
+ belongs_to :category, foreign_key: "c_id", class_name: 'Category'
396
+ end
397
+
398
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
399
+ migrate_up(<<~EOS.strip)
400
+ add_column :adverts, :c_id, :integer, limit: 8, null: false
401
+
402
+ add_index :adverts, [:c_id], name: 'on_c_id'
403
+
404
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
405
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
406
+ EOS
407
+ )
408
+
409
+ Advert.field_specs.delete(:c_id)
410
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
305
411
 
306
- # Now migrate to an unstated text limit:
412
+ # You can avoid generating the index by specifying `index: false`
413
+
414
+ class Category < ActiveRecord::Base; end
415
+ class Advert < ActiveRecord::Base
416
+ fields { }
417
+ belongs_to :category, index: false
418
+ end
419
+
420
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
421
+ migrate_up(<<~EOS.strip)
422
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
423
+
424
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
425
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
426
+ EOS
427
+ )
428
+
429
+ Advert.field_specs.delete(:category_id)
430
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
431
+
432
+ # You can specify the index name with :index
433
+
434
+ class Category < ActiveRecord::Base; end
435
+ class Advert < ActiveRecord::Base
436
+ fields { }
437
+ belongs_to :category, index: 'my_index'
438
+ end
439
+
440
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
441
+ migrate_up(<<~EOS.strip)
442
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
443
+
444
+ add_index :adverts, [:category_id], name: 'my_index'
445
+
446
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
447
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
448
+ EOS
449
+ )
450
+
451
+ Advert.field_specs.delete(:category_id)
452
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
453
+
454
+ ### Timestamps and Optimimistic Locking
455
+
456
+ # `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
457
+ # Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
307
458
 
308
459
  class Advert < ActiveRecord::Base
309
460
  fields do
310
- description :text
461
+ timestamps
462
+ optimistic_lock
311
463
  end
312
464
  end
313
465
 
314
466
  expect(Generators::DeclareSchema::Migration::Migrator.run).to(
315
467
  migrate_up(<<~EOS.strip)
316
- change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
468
+ add_column :adverts, :created_at, :datetime, null: true
469
+ add_column :adverts, :updated_at, :datetime, null: true
470
+ add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
471
+
472
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
473
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
317
474
  EOS
318
475
  .and migrate_down(<<~EOS.strip)
319
- change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
476
+ remove_column :adverts, :created_at
477
+ remove_column :adverts, :updated_at
478
+ remove_column :adverts, :lock_version
479
+
480
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
481
+ "remove_foreign_key(\"adverts\", name: \"on_c_id\")" if defined?(Mysql2)}
482
+ EOS
483
+ )
484
+
485
+ Advert.field_specs.delete(:updated_at)
486
+ Advert.field_specs.delete(:created_at)
487
+ Advert.field_specs.delete(:lock_version)
488
+
489
+ ### Indices
490
+
491
+ # You can add an index to a field definition
492
+
493
+ class Advert < ActiveRecord::Base
494
+ fields do
495
+ title :string, index: true, limit: 250, null: true
496
+ end
497
+ end
498
+
499
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
500
+ migrate_up(<<~EOS.strip)
501
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
502
+
503
+ add_index :adverts, [:title], name: 'on_title'
504
+
505
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
506
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
507
+ EOS
508
+ )
509
+
510
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
511
+
512
+ # You can ask for a unique index
513
+
514
+ class Advert < ActiveRecord::Base
515
+ fields do
516
+ title :string, index: true, unique: true, null: true, limit: 250
517
+ end
518
+ end
519
+
520
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
521
+ migrate_up(<<~EOS.strip)
522
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
523
+
524
+ add_index :adverts, [:title], unique: true, name: 'on_title'
525
+
526
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
527
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
528
+ EOS
529
+ )
530
+
531
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
532
+
533
+ # You can specify the name for the index
534
+
535
+ class Advert < ActiveRecord::Base
536
+ fields do
537
+ title :string, index: 'my_index', limit: 250, null: true
538
+ end
539
+ end
540
+
541
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
542
+ migrate_up(<<~EOS.strip)
543
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
544
+
545
+ add_index :adverts, [:title], name: 'my_index'
546
+
547
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
548
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
549
+ EOS
550
+ )
551
+
552
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
553
+
554
+ # You can ask for an index outside of the fields block
555
+
556
+ class Advert < ActiveRecord::Base
557
+ index :title
558
+ end
559
+
560
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
561
+ migrate_up(<<~EOS.strip)
562
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
563
+
564
+ add_index :adverts, [:title], name: 'on_title'
565
+
566
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
567
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
320
568
  EOS
321
569
  )
322
570
 
323
- # And migrate to a stated text limit that is the same as the unstated one:
571
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
572
+
573
+ # The available options for the index function are `:unique` and `:name`
574
+
575
+ class Advert < ActiveRecord::Base
576
+ index :title, unique: true, name: 'my_index'
577
+ end
578
+
579
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
580
+ migrate_up(<<~EOS.strip)
581
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
582
+
583
+ add_index :adverts, [:title], unique: true, name: 'my_index'
584
+
585
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
586
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
587
+ EOS
588
+ )
589
+
590
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
591
+
592
+ # You can create an index on more than one field
593
+
594
+ class Advert < ActiveRecord::Base
595
+ index [:title, :category_id]
596
+ end
597
+
598
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
599
+ migrate_up(<<~EOS.strip)
600
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
601
+
602
+ add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
603
+
604
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
605
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
606
+ EOS
607
+ )
608
+
609
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
610
+
611
+ # Finally, you can specify that the migration generator should completely ignore an
612
+ # index by passing its name to ignore_index in the model.
613
+ # This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
614
+
615
+ ### Rename a table
616
+
617
+ # The migration generator respects the `set_table_name` declaration, although as before, we need to explicitly tell the generator that we want a rename rather than a create and a drop.
618
+
619
+ class Advert < ActiveRecord::Base
620
+ self.table_name = "ads"
621
+ fields do
622
+ title :string, limit: 250, null: true
623
+ body :text, null: true
624
+ end
625
+ end
626
+
627
+ Advert.connection.schema_cache.clear!
628
+ Advert.reset_column_information
629
+
630
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
631
+ migrate_up(<<~EOS.strip)
632
+ rename_table :adverts, :ads
633
+
634
+ add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
635
+ add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
636
+
637
+ #{if defined?(SQLite3)
638
+ "add_index :ads, [:id], unique: true, name: 'PRIMARY'\n"
639
+ elsif defined?(Mysql2)
640
+ "execute \"ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
641
+ "add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
642
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")"
643
+ end}
644
+ EOS
645
+ .and migrate_down(<<~EOS.strip)
646
+ remove_column :ads, :title
647
+ remove_column :ads, :body
648
+
649
+ rename_table :ads, :adverts
650
+
651
+ #{if defined?(SQLite3)
652
+ "add_index :adverts, [:id], unique: true, name: 'PRIMARY'\n"
653
+ elsif defined?(Mysql2)
654
+ "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
655
+ "remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
656
+ "remove_foreign_key(\"adverts\", name: \"on_c_id\")"
657
+ end}
658
+ EOS
659
+ )
660
+
661
+ # Set the table name back to what it should be and confirm we're in sync:
662
+
663
+ nuke_model_class(Advert)
664
+
665
+ class Advert < ActiveRecord::Base
666
+ self.table_name = "adverts"
667
+ end
668
+
669
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
670
+
671
+ ### Rename a table
672
+
673
+ # As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
674
+
675
+ nuke_model_class(Advert)
676
+
677
+ class Advertisement < ActiveRecord::Base
678
+ fields do
679
+ title :string, limit: 250, null: true
680
+ body :text, null: true
681
+ end
682
+ end
683
+
684
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
685
+ migrate_up(<<~EOS.strip)
686
+ rename_table :adverts, :advertisements
687
+
688
+ add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
689
+ add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
690
+ remove_column :advertisements, :name
691
+
692
+ #{if defined?(SQLite3)
693
+ "add_index :advertisements, [:id], unique: true, name: 'PRIMARY'"
694
+ elsif defined?(Mysql2)
695
+ "execute \"ALTER TABLE advertisements DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
696
+ end}
697
+ EOS
698
+ .and migrate_down(<<~EOS.strip)
699
+ remove_column :advertisements, :title
700
+ remove_column :advertisements, :body
701
+ add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
702
+
703
+ rename_table :advertisements, :adverts
704
+
705
+ #{if defined?(SQLite3)
706
+ "add_index :adverts, [:id], unique: true, name: 'PRIMARY'"
707
+ elsif defined?(Mysql2)
708
+ "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
709
+ end}
710
+ EOS
711
+ )
712
+
713
+ ### Drop a table
714
+
715
+ nuke_model_class(Advertisement)
716
+
717
+ # If you delete a model, the migration generator will create a `drop_table` migration.
718
+
719
+ # Dropping tables is where the automatic down-migration really comes in handy:
720
+
721
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
722
+ migrate_up(<<~EOS.strip)
723
+ drop_table :adverts
724
+ EOS
725
+ .and migrate_down(<<~EOS.strip)
726
+ create_table "adverts"#{table_options}, force: :cascade do |t|
727
+ t.string "name", limit: 250#{charset_and_collation}
728
+ end
729
+ EOS
730
+ )
731
+
732
+ ## STI
733
+
734
+ ### Adding an STI subclass
735
+
736
+ # Adding a subclass or two should introduce the 'type' column and no other changes
737
+
738
+ class Advert < ActiveRecord::Base
739
+ fields do
740
+ body :text, null: true
741
+ title :string, default: "Untitled", limit: 250, null: true
742
+ end
743
+ end
744
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
745
+ ActiveRecord::Migration.class_eval(up)
746
+
747
+ class FancyAdvert < Advert
748
+ end
749
+ class SuperFancyAdvert < FancyAdvert
750
+ end
751
+
752
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
753
+ expect(migrations).to(
754
+ migrate_up(<<~EOS.strip)
755
+ add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
756
+
757
+ add_index :adverts, [:type], name: 'on_type'
758
+ EOS
759
+ .and migrate_down(<<~EOS.strip)
760
+ remove_column :adverts, :type
761
+
762
+ remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
763
+ EOS
764
+ )
765
+ end
766
+
767
+ Advert.field_specs.delete(:type)
768
+ nuke_model_class(SuperFancyAdvert)
769
+ nuke_model_class(FancyAdvert)
770
+ Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
771
+
772
+ ## Coping with multiple changes
773
+
774
+ # The migration generator is designed to create complete migrations even if many changes to the models have taken place.
775
+
776
+ # First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
777
+
778
+ ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
779
+ Advert.connection.schema_cache.clear!
780
+ Advert.reset_column_information
781
+
782
+ expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
783
+ to eq(["adverts"])
784
+ expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
785
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
786
+
787
+
788
+ ### Rename a column and change the default
789
+
790
+ Advert.field_specs.clear
791
+
792
+ class Advert < ActiveRecord::Base
793
+ fields do
794
+ name :string, default: "No Name", limit: 250, null: true
795
+ body :text, null: true
796
+ end
797
+ end
798
+
799
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
800
+ migrate_up(<<~EOS.strip)
801
+ rename_column :adverts, :title, :name
802
+ change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
803
+ EOS
804
+ .and migrate_down(<<~EOS.strip)
805
+ rename_column :adverts, :name, :title
806
+ change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
807
+ EOS
808
+ )
809
+
810
+ ### Rename a table and add a column
811
+
812
+ nuke_model_class(Advert)
813
+ class Ad < ActiveRecord::Base
814
+ fields do
815
+ title :string, default: "Untitled", limit: 250
816
+ body :text, null: true
817
+ created_at :datetime
818
+ end
819
+ end
820
+
821
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
822
+ migrate_up(<<~EOS.strip)
823
+ rename_table :adverts, :ads
824
+
825
+ add_column :ads, :created_at, :datetime, null: false
826
+ change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
827
+
828
+ #{if defined?(SQLite3)
829
+ "add_index :ads, [:id], unique: true, name: 'PRIMARY'"
830
+ elsif defined?(Mysql2)
831
+ 'execute "ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)"'
832
+ end}
833
+ EOS
834
+ )
835
+
836
+ class Advert < ActiveRecord::Base
837
+ fields do
838
+ body :text, null: true
839
+ title :string, default: "Untitled", limit: 250, null: true
840
+ end
841
+ end
842
+
843
+ ## Legacy Keys
844
+
845
+ # DeclareSchema has some support for legacy keys.
846
+
847
+ nuke_model_class(Ad)
848
+
849
+ class Advert < ActiveRecord::Base
850
+ fields do
851
+ body :text, null: true
852
+ end
853
+ self.primary_key = "advert_id"
854
+ end
855
+
856
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
857
+ migrate_up(<<~EOS.strip)
858
+ rename_column :adverts, :id, :advert_id
859
+
860
+ #{if defined?(SQLite3)
861
+ "add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY'"
862
+ elsif defined?(Mysql2)
863
+ 'execute "ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (advert_id)"'
864
+ end}
865
+ EOS
866
+ )
867
+
868
+ nuke_model_class(Advert)
869
+ ActiveRecord::Base.connection.execute("drop table `adverts`;")
870
+
871
+ ## DSL
872
+
873
+ # The DSL allows lambdas and constants
874
+
875
+ class User < ActiveRecord::Base
876
+ fields do
877
+ company :string, limit: 250, ruby_default: -> { "BigCorp" }
878
+ end
879
+ end
880
+ expect(User.field_specs.keys).to eq(['company'])
881
+ expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
882
+
883
+ ## validates
884
+
885
+ # DeclareSchema can accept a validates hash in the field options.
886
+
887
+ class Ad < ActiveRecord::Base
888
+ class << self
889
+ def validates(field_name, options)
890
+ end
891
+ end
892
+ end
893
+ expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
894
+ class Ad < ActiveRecord::Base
895
+ fields do
896
+ company :string, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
897
+ end
898
+ self.primary_key = "advert_id"
899
+ end
900
+ up, _down = Generators::DeclareSchema::Migration::Migrator.run
901
+ ActiveRecord::Migration.class_eval(up)
902
+ expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
903
+ end
904
+
905
+ describe 'serialize' do
906
+ before do
907
+ class Ad < ActiveRecord::Base
908
+ @serialize_args = []
909
+
910
+ class << self
911
+ attr_reader :serialize_args
912
+
913
+ def serialize(*args)
914
+ @serialize_args << args
915
+ end
916
+ end
917
+ end
918
+ end
919
+
920
+ describe 'untyped' do
921
+ it 'allows serialize: true' do
922
+ class Ad < ActiveRecord::Base
923
+ fields do
924
+ allow_list :text, limit: 0xFFFF, serialize: true
925
+ end
926
+ end
927
+
928
+ expect(Ad.serialize_args).to eq([[:allow_list]])
929
+ end
930
+
931
+ it 'converts defaults with .to_yaml' do
932
+ class Ad < ActiveRecord::Base
933
+ fields do
934
+ allow_list :string, limit: 250, serialize: true, null: true, default: []
935
+ allow_hash :string, limit: 250, serialize: true, null: true, default: {}
936
+ allow_string :string, limit: 250, serialize: true, null: true, default: ['abc']
937
+ allow_null :string, limit: 250, serialize: true, null: true, default: nil
938
+ end
939
+ end
940
+
941
+ expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
942
+ expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
943
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
944
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
945
+ end
946
+ end
947
+
948
+ describe 'Array' do
949
+ it 'allows serialize: Array' do
950
+ class Ad < ActiveRecord::Base
951
+ fields do
952
+ allow_list :string, limit: 250, serialize: Array, null: true
953
+ end
954
+ end
955
+
956
+ expect(Ad.serialize_args).to eq([[:allow_list, Array]])
957
+ end
958
+
959
+ it 'allows Array defaults' do
960
+ class Ad < ActiveRecord::Base
961
+ fields do
962
+ allow_list :string, limit: 250, serialize: Array, null: true, default: [2]
963
+ allow_string :string, limit: 250, serialize: Array, null: true, default: ['abc']
964
+ allow_empty :string, limit: 250, serialize: Array, null: true, default: []
965
+ allow_null :string, limit: 250, serialize: Array, null: true, default: nil
966
+ end
967
+ end
968
+
969
+ expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
970
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
971
+ expect(Ad.field_specs['allow_empty'].default).to eq(nil)
972
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
973
+ end
974
+ end
975
+
976
+ describe 'Hash' do
977
+ it 'allows serialize: Hash' do
978
+ class Ad < ActiveRecord::Base
979
+ fields do
980
+ allow_list :string, limit: 250, serialize: Hash, null: true
981
+ end
982
+ end
983
+
984
+ expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
985
+ end
986
+
987
+ it 'allows Hash defaults' do
988
+ class Ad < ActiveRecord::Base
989
+ fields do
990
+ allow_loc :string, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
991
+ allow_hash :string, limit: 250, serialize: Hash, null: true, default: {}
992
+ allow_null :string, limit: 250, serialize: Hash, null: true, default: nil
993
+ end
994
+ end
995
+
996
+ expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
997
+ expect(Ad.field_specs['allow_hash'].default).to eq(nil)
998
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
999
+ end
1000
+ end
1001
+
1002
+ describe 'JSON' do
1003
+ it 'allows serialize: JSON' do
1004
+ class Ad < ActiveRecord::Base
1005
+ fields do
1006
+ allow_list :string, limit: 250, serialize: JSON
1007
+ end
1008
+ end
1009
+
1010
+ expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
1011
+ end
1012
+
1013
+ it 'allows JSON defaults' do
1014
+ class Ad < ActiveRecord::Base
1015
+ fields do
1016
+ allow_hash :string, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
1017
+ allow_empty_array :string, limit: 250, serialize: JSON, null: true, default: []
1018
+ allow_empty_hash :string, limit: 250, serialize: JSON, null: true, default: {}
1019
+ allow_null :string, limit: 250, serialize: JSON, null: true, default: nil
1020
+ end
1021
+ end
1022
+
1023
+ expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
1024
+ expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
1025
+ expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
1026
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
1027
+ end
1028
+ end
1029
+
1030
+ class ValueClass
1031
+ delegate :present?, :inspect, to: :@value
1032
+
1033
+ def initialize(value)
1034
+ @value = value
1035
+ end
1036
+
1037
+ class << self
1038
+ def dump(object)
1039
+ if object&.present?
1040
+ object.inspect
1041
+ end
1042
+ end
1043
+
1044
+ def load(serialized)
1045
+ if serialized
1046
+ raise 'not used ???'
1047
+ end
1048
+ end
1049
+ end
1050
+ end
1051
+
1052
+ describe 'custom coder' do
1053
+ it 'allows serialize: ValueClass' do
1054
+ class Ad < ActiveRecord::Base
1055
+ fields do
1056
+ allow_list :string, limit: 250, serialize: ValueClass
1057
+ end
1058
+ end
1059
+
1060
+ expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
1061
+ end
1062
+
1063
+ it 'allows ValueClass defaults' do
1064
+ class Ad < ActiveRecord::Base
1065
+ fields do
1066
+ allow_hash :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
1067
+ allow_empty_array :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
1068
+ allow_null :string, limit: 250, serialize: ValueClass, null: true, default: nil
1069
+ end
1070
+ end
1071
+
1072
+ expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
1073
+ expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
1074
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
1075
+ end
1076
+ end
1077
+
1078
+ it 'disallows serialize: with a non-string column type' do
1079
+ expect do
1080
+ class Ad < ActiveRecord::Base
1081
+ fields do
1082
+ allow_list :integer, limit: 8, serialize: true
1083
+ end
1084
+ end
1085
+ end.to raise_exception(ArgumentError, /must be :string or :text/)
1086
+ end
1087
+ end
1088
+
1089
+ context "for Rails #{Rails::VERSION::MAJOR}" do
1090
+ if Rails::VERSION::MAJOR >= 5
1091
+ let(:optional_true) { { optional: true } }
1092
+ let(:optional_false) { { optional: false } }
1093
+ else
1094
+ let(:optional_true) { {} }
1095
+ let(:optional_false) { {} }
1096
+ end
1097
+ let(:optional_flag) { { false => optional_false, true => optional_true } }
1098
+
1099
+ describe 'belongs_to' do
1100
+ before do
1101
+ unless defined?(AdCategory)
1102
+ class AdCategory < ActiveRecord::Base
1103
+ fields { }
1104
+ end
1105
+ end
1106
+
1107
+ class Advert < ActiveRecord::Base
1108
+ fields do
1109
+ name :string, limit: 250, null: true
1110
+ category_id :integer, limit: 8
1111
+ nullable_category_id :integer, limit: 8, null: true
1112
+ end
1113
+ end
1114
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
1115
+ ActiveRecord::Migration.class_eval(up)
1116
+ end
1117
+
1118
+ it 'passes through optional: when given' do
1119
+ class AdvertBelongsTo < ActiveRecord::Base
1120
+ self.table_name = 'adverts'
1121
+ fields { }
1122
+ reset_column_information
1123
+ belongs_to :ad_category, optional: true
1124
+ end
1125
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1126
+ end
1127
+
1128
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
1129
+ it 'passes through optional: true, null: false' do
1130
+ class AdvertBelongsTo < ActiveRecord::Base
1131
+ self.table_name = 'adverts'
1132
+ fields { }
1133
+ reset_column_information
1134
+ belongs_to :ad_category, optional: true, null: false
1135
+ end
1136
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1137
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
1138
+ end
1139
+
1140
+ it 'passes through optional: false, null: true' do
1141
+ class AdvertBelongsTo < ActiveRecord::Base
1142
+ self.table_name = 'adverts'
1143
+ fields { }
1144
+ reset_column_information
1145
+ belongs_to :ad_category, optional: false, null: true
1146
+ end
1147
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
1148
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
1149
+ end
1150
+ end
1151
+
1152
+ [false, true].each do |nullable|
1153
+ context "nullable=#{nullable}" do
1154
+ it 'infers optional: from null:' do
1155
+ eval <<~EOS
1156
+ class AdvertBelongsTo < ActiveRecord::Base
1157
+ fields { }
1158
+ belongs_to :ad_category, null: #{nullable}
1159
+ end
1160
+ EOS
1161
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1162
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1163
+ end
1164
+
1165
+ it 'infers null: from optional:' do
1166
+ eval <<~EOS
1167
+ class AdvertBelongsTo < ActiveRecord::Base
1168
+ fields { }
1169
+ belongs_to :ad_category, optional: #{nullable}
1170
+ end
1171
+ EOS
1172
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1173
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
1174
+ end
1175
+ end
1176
+ end
1177
+ end
1178
+ end
1179
+
1180
+ describe 'migration base class' do
1181
+ it 'adapts to Rails 4' do
1182
+ class Advert < active_record_base_class.constantize
1183
+ fields do
1184
+ title :string, limit: 100
1185
+ end
1186
+ end
1187
+
1188
+ generate_migrations '-n', '-m'
1189
+
1190
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1191
+ expect(migrations.size).to eq(1), migrations.inspect
1192
+
1193
+ migration_content = File.read(migrations.first)
1194
+ first_line = migration_content.split("\n").first
1195
+ base_class = first_line.split(' < ').last
1196
+ expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
1197
+ end
1198
+ end
1199
+
1200
+ context 'Does not generate migrations' do
1201
+ it 'for aliased fields bigint -> integer limit 8' do
1202
+ if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
1203
+ class Advert < active_record_base_class.constantize
1204
+ fields do
1205
+ price :bigint
1206
+ end
1207
+ end
1208
+
1209
+ generate_migrations '-n', '-m'
1210
+
1211
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1212
+ expect(migrations.size).to eq(1), migrations.inspect
1213
+
1214
+ if defined?(Mysql2) && Rails::VERSION::MAJOR < 5
1215
+ ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
1216
+ end
1217
+
1218
+ class Advert < active_record_base_class.constantize
1219
+ fields do
1220
+ price :integer, limit: 8
1221
+ end
1222
+ end
1223
+
1224
+ expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
1225
+ end
1226
+ end
1227
+ end
1228
+ end
1229
+
1230
+ context 'Using declare_schema' do
1231
+ # DeclareSchema - Migration Generator
1232
+ it 'generates migrations' do
1233
+ ## The migration generator -- introduction
1234
+
1235
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
1236
+
1237
+ class Advert < ActiveRecord::Base
1238
+ end
1239
+
1240
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to migrate_up("").and migrate_down("")
1241
+
1242
+ Generators::DeclareSchema::Migration::Migrator.ignore_tables = ["green_fishes"]
1243
+
1244
+ Advert.connection.schema_cache.clear!
1245
+ Advert.reset_column_information
1246
+
1247
+ class Advert < ActiveRecord::Base
1248
+ declare_schema do
1249
+ string :name, limit: 250, null: true
1250
+ end
1251
+ end
1252
+
1253
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
1254
+ expect(migrations).to(
1255
+ migrate_up(<<~EOS.strip)
1256
+ create_table :adverts, id: :bigint do |t|
1257
+ t.string :name, limit: 250, null: true#{charset_and_collation}
1258
+ end#{charset_alter_table}
1259
+ EOS
1260
+ .and migrate_down("drop_table :adverts")
1261
+ )
1262
+ end
1263
+
1264
+ ActiveRecord::Migration.class_eval(up)
1265
+ expect(Advert.columns.map(&:name)).to eq(["id", "name"])
1266
+
1267
+ if Rails::VERSION::MAJOR < 5
1268
+ # Rails 4 drivers don't always create PK properly. Fix that by dropping and recreating.
1269
+ ActiveRecord::Base.connection.execute("drop table adverts")
1270
+ if defined?(Mysql2)
1271
+ ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTO_INCREMENT NOT NULL, name varchar(250)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin")
1272
+ else
1273
+ ActiveRecord::Base.connection.execute("CREATE TABLE adverts (id integer PRIMARY KEY AUTOINCREMENT NOT NULL, name varchar(250))")
1274
+ end
1275
+ end
1276
+
1277
+ class Advert < ActiveRecord::Base
1278
+ declare_schema do
1279
+ string :name, limit: 250, null: true
1280
+ text :body, null: true
1281
+ datetime :published_at, null: true
1282
+ end
1283
+ end
1284
+
1285
+ Advert.connection.schema_cache.clear!
1286
+ Advert.reset_column_information
1287
+
1288
+ expect(migrate).to(
1289
+ migrate_up(<<~EOS.strip)
1290
+ add_column :adverts, :body, :text#{text_limit}, null: true#{charset_and_collation}
1291
+ add_column :adverts, :published_at, :datetime, null: true
1292
+ EOS
1293
+ .and migrate_down(<<~EOS.strip)
1294
+ remove_column :adverts, :body
1295
+ remove_column :adverts, :published_at
1296
+ EOS
1297
+ )
1298
+
1299
+ Advert.field_specs.clear # not normally needed
1300
+ class Advert < ActiveRecord::Base
1301
+ declare_schema do
1302
+ string :name, limit: 250, null: true
1303
+ text :body, null: true
1304
+ end
1305
+ end
1306
+
1307
+ expect(migrate).to(
1308
+ migrate_up("remove_column :adverts, :published_at").and(
1309
+ migrate_down("add_column :adverts, :published_at, :datetime#{datetime_precision}, null: true")
1310
+ )
1311
+ )
1312
+
1313
+ nuke_model_class(Advert)
1314
+ class Advert < ActiveRecord::Base
1315
+ declare_schema do
1316
+ string :title, limit: 250, null: true
1317
+ text :body, null: true
1318
+ end
1319
+ end
1320
+
1321
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1322
+ migrate_up(<<~EOS.strip)
1323
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1324
+ remove_column :adverts, :name
1325
+ EOS
1326
+ .and migrate_down(<<~EOS.strip)
1327
+ remove_column :adverts, :title
1328
+ add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
1329
+ EOS
1330
+ )
1331
+
1332
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { name: :title })).to(
1333
+ migrate_up("rename_column :adverts, :name, :title").and(
1334
+ migrate_down("rename_column :adverts, :title, :name")
1335
+ )
1336
+ )
1337
+
1338
+ migrate
1339
+
1340
+ class Advert < ActiveRecord::Base
1341
+ declare_schema do
1342
+ text :title, null: true
1343
+ text :body, null: true
1344
+ end
1345
+ end
1346
+
1347
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1348
+ migrate_up("change_column :adverts, :title, :text#{text_limit}, null: true#{charset_and_collation}").and(
1349
+ migrate_down("change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}")
1350
+ )
1351
+ )
1352
+
1353
+ class Advert < ActiveRecord::Base
1354
+ declare_schema do
1355
+ string :title, default: "Untitled", limit: 250, null: true
1356
+ text :body, null: true
1357
+ end
1358
+ end
1359
+
1360
+ expect(migrate).to(
1361
+ migrate_up(<<~EOS.strip)
1362
+ change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
1363
+ EOS
1364
+ .and migrate_down(<<~EOS.strip)
1365
+ change_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1366
+ EOS
1367
+ )
1368
+
1369
+ ### Limits
1370
+
1371
+ class Advert < ActiveRecord::Base
1372
+ declare_schema do
1373
+ integer :price, null: true, limit: 2
1374
+ end
1375
+ end
1376
+
1377
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run.tap do |migrations|
1378
+ expect(migrations).to migrate_up("add_column :adverts, :price, :integer, limit: 2, null: true")
1379
+ end
1380
+
1381
+ # Now run the migration, then change the limit:
1382
+
1383
+ ActiveRecord::Migration.class_eval(up)
1384
+ class Advert < ActiveRecord::Base
1385
+ declare_schema do
1386
+ integer :price, null: true, limit: 3
1387
+ end
1388
+ end
1389
+
1390
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1391
+ migrate_up(<<~EOS.strip)
1392
+ change_column :adverts, :price, :integer, limit: 3, null: true
1393
+ EOS
1394
+ .and migrate_down(<<~EOS.strip)
1395
+ change_column :adverts, :price, :integer, limit: 2, null: true
1396
+ EOS
1397
+ )
1398
+
1399
+ ActiveRecord::Migration.class_eval("remove_column :adverts, :price")
1400
+ class Advert < ActiveRecord::Base
1401
+ declare_schema do
1402
+ decimal :price, precision: 4, scale: 1, null: true
1403
+ end
1404
+ end
1405
+
1406
+ # Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
1407
+ # allowed for that database type (0xffffffff for LONGTEXT in MySQL unlimited in Postgres, 1 billion in Sqlite).
1408
+ # If a `limit` is given, it will only be used in MySQL, to choose the smallest TEXT field that will accommodate
1409
+ # that limit (0xff for TINYTEXT, 0xffff for TEXT, 0xffffff for MEDIUMTEXT, 0xffffffff for LONGTEXT).
1410
+
1411
+ if defined?(SQLite3)
1412
+ expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_falsey
1413
+ end
1414
+
1415
+ class Advert < ActiveRecord::Base
1416
+ declare_schema do
1417
+ text :notes
1418
+ text :description, limit: 30000
1419
+ end
1420
+ end
1421
+
1422
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1423
+ migrate_up(<<~EOS.strip)
1424
+ add_column :adverts, :price, :decimal, precision: 4, scale: 1, null: true
1425
+ add_column :adverts, :notes, :text#{text_limit}, null: false#{charset_and_collation}
1426
+ add_column :adverts, :description, :text#{', limit: 65535' if defined?(Mysql2)}, null: false#{charset_and_collation}
1427
+ EOS
1428
+ )
1429
+
1430
+ Advert.field_specs.delete :price
1431
+ Advert.field_specs.delete :notes
1432
+ Advert.field_specs.delete :description
1433
+
1434
+ # In MySQL, limits are applied, rounded up:
1435
+
1436
+ if defined?(Mysql2)
1437
+ expect(::DeclareSchema::Model::FieldSpec.mysql_text_limits?).to be_truthy
1438
+
1439
+ class Advert < ActiveRecord::Base
1440
+ declare_schema do
1441
+ text :notes
1442
+ text :description, limit: 250
1443
+ end
1444
+ end
1445
+
1446
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1447
+ migrate_up(<<~EOS.strip)
1448
+ add_column :adverts, :notes, :text, limit: 4294967295, null: false#{charset_and_collation}
1449
+ add_column :adverts, :description, :text, limit: 255, null: false#{charset_and_collation}
1450
+ EOS
1451
+ )
1452
+
1453
+ Advert.field_specs.delete :notes
1454
+
1455
+ # Limits that are too high for MySQL will raise an exception.
1456
+
1457
+ expect do
1458
+ class Advert < ActiveRecord::Base
1459
+ declare_schema do
1460
+ text :notes
1461
+ text :description, limit: 0x1_0000_0000
1462
+ end
1463
+ end
1464
+ end.to raise_exception(ArgumentError, "limit of 4294967296 is too large for MySQL")
1465
+
1466
+ Advert.field_specs.delete :notes
1467
+
1468
+ # And in MySQL, unstated text limits are treated as the maximum (LONGTEXT) limit.
1469
+
1470
+ # To start, we'll set the database schema for `description` to match the above limit of 250.
1471
+
1472
+ Advert.connection.execute "ALTER TABLE adverts ADD COLUMN description TINYTEXT"
1473
+ Advert.connection.schema_cache.clear!
1474
+ Advert.reset_column_information
1475
+ expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
1476
+ to eq(["adverts"])
1477
+ expect(Advert.columns.map(&:name)).to eq(["id", "body", "title", "description"])
1478
+
1479
+ # Now migrate to an unstated text limit:
1480
+
1481
+ class Advert < ActiveRecord::Base
1482
+ declare_schema do
1483
+ text :description
1484
+ end
1485
+ end
1486
+
1487
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1488
+ migrate_up(<<~EOS.strip)
1489
+ change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
1490
+ EOS
1491
+ .and migrate_down(<<~EOS.strip)
1492
+ change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
1493
+ EOS
1494
+ )
1495
+
1496
+ # And migrate to a stated text limit that is the same as the unstated one:
324
1497
 
325
- class Advert < ActiveRecord::Base
326
- fields do
327
- description :text, limit: 0xffffffff
1498
+ class Advert < ActiveRecord::Base
1499
+ declare_schema do
1500
+ text :description, limit: 0xffffffff
1501
+ end
328
1502
  end
329
- end
330
1503
 
331
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
332
- migrate_up(<<~EOS.strip)
333
- change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
334
- EOS
335
- .and migrate_down(<<~EOS.strip)
336
- change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
1504
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1505
+ migrate_up(<<~EOS.strip)
1506
+ change_column :adverts, :description, :text, limit: 4294967295, null: false#{charset_and_collation}
1507
+ EOS
1508
+ .and migrate_down(<<~EOS.strip)
1509
+ change_column :adverts, :description, :text#{', limit: 255' if defined?(Mysql2)}, null: true#{charset_and_collation}
337
1510
  EOS
338
- )
339
- end
1511
+ )
1512
+ end
340
1513
 
341
- Advert.field_specs.clear
342
- Advert.connection.schema_cache.clear!
343
- Advert.reset_column_information
344
- class Advert < ActiveRecord::Base
345
- fields do
346
- name :string, limit: 250, null: true
1514
+ Advert.field_specs.clear
1515
+ Advert.connection.schema_cache.clear!
1516
+ Advert.reset_column_information
1517
+ class Advert < ActiveRecord::Base
1518
+ declare_schema do
1519
+ string :name, limit: 250, null: true
1520
+ end
347
1521
  end
348
- end
349
1522
 
350
- up = Generators::DeclareSchema::Migration::Migrator.run.first
351
- ActiveRecord::Migration.class_eval up
1523
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
1524
+ ActiveRecord::Migration.class_eval up
352
1525
 
353
- Advert.connection.schema_cache.clear!
354
- Advert.reset_column_information
1526
+ Advert.connection.schema_cache.clear!
1527
+ Advert.reset_column_information
355
1528
 
356
- ### Foreign Keys
1529
+ ### Foreign Keys
357
1530
 
358
- # DeclareSchema extends the `belongs_to` macro so that it also declares the
359
- # foreign-key field. It also generates an index on the field.
1531
+ # DeclareSchema extends the `belongs_to` macro so that it also declares the
1532
+ # foreign-key field. It also generates an index on the field.
360
1533
 
361
- class Category < ActiveRecord::Base; end
362
- class Advert < ActiveRecord::Base
363
- fields do
364
- name :string, limit: 250, null: true
1534
+ class Category < ActiveRecord::Base; end
1535
+ class Advert < ActiveRecord::Base
1536
+ declare_schema do
1537
+ string :name, limit: 250, null: true
1538
+ end
1539
+ belongs_to :category
365
1540
  end
366
- belongs_to :category
367
- end
368
1541
 
369
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
370
- migrate_up(<<~EOS.strip)
371
- add_column :adverts, :category_id, :integer, limit: 8, null: false
1542
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1543
+ migrate_up(<<~EOS.strip)
1544
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
372
1545
 
373
- add_index :adverts, [:category_id], name: 'on_category_id'
1546
+ add_index :adverts, [:category_id], name: 'on_category_id'
374
1547
 
375
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" if defined?(Mysql2)}
376
- EOS
377
- .and migrate_down(<<~EOS.strip)
378
- remove_column :adverts, :category_id
1548
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" if defined?(Mysql2)}
1549
+ EOS
1550
+ .and migrate_down(<<~EOS.strip)
1551
+ remove_column :adverts, :category_id
379
1552
 
380
- remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
1553
+ remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
381
1554
 
382
- #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" if defined?(Mysql2)}
1555
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" if defined?(Mysql2)}
383
1556
  EOS
384
- )
1557
+ )
385
1558
 
386
- Advert.field_specs.delete(:category_id)
387
- Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
1559
+ Advert.field_specs.delete(:category_id)
1560
+ Advert.index_definitions.delete_if { |spec| spec.fields==["category_id"] }
388
1561
 
389
- # If you specify a custom foreign key, the migration generator observes that:
1562
+ # If you specify a custom foreign key, the migration generator observes that:
390
1563
 
391
- class Category < ActiveRecord::Base; end
392
- class Advert < ActiveRecord::Base
393
- fields { }
394
- belongs_to :category, foreign_key: "c_id", class_name: 'Category'
395
- end
1564
+ class Category < ActiveRecord::Base; end
1565
+ class Advert < ActiveRecord::Base
1566
+ declare_schema { }
1567
+ belongs_to :category, foreign_key: "c_id", class_name: 'Category'
1568
+ end
396
1569
 
397
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
398
- migrate_up(<<~EOS.strip)
399
- add_column :adverts, :c_id, :integer, limit: 8, null: false
1570
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1571
+ migrate_up(<<~EOS.strip)
1572
+ add_column :adverts, :c_id, :integer, limit: 8, null: false
400
1573
 
401
- add_index :adverts, [:c_id], name: 'on_c_id'
1574
+ add_index :adverts, [:c_id], name: 'on_c_id'
402
1575
 
403
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1576
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
404
1577
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
405
1578
  EOS
406
- )
1579
+ )
407
1580
 
408
- Advert.field_specs.delete(:c_id)
409
- Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
1581
+ Advert.field_specs.delete(:c_id)
1582
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["c_id"] }
410
1583
 
411
- # You can avoid generating the index by specifying `index: false`
1584
+ # You can avoid generating the index by specifying `index: false`
412
1585
 
413
- class Category < ActiveRecord::Base; end
414
- class Advert < ActiveRecord::Base
415
- fields { }
416
- belongs_to :category, index: false
417
- end
1586
+ class Category < ActiveRecord::Base; end
1587
+ class Advert < ActiveRecord::Base
1588
+ declare_schema { }
1589
+ belongs_to :category, index: false
1590
+ end
418
1591
 
419
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
420
- migrate_up(<<~EOS.strip)
421
- add_column :adverts, :category_id, :integer, limit: 8, null: false
1592
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1593
+ migrate_up(<<~EOS.strip)
1594
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
422
1595
 
423
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1596
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
424
1597
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
425
1598
  EOS
426
- )
1599
+ )
427
1600
 
428
- Advert.field_specs.delete(:category_id)
429
- Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
1601
+ Advert.field_specs.delete(:category_id)
1602
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
430
1603
 
431
- # You can specify the index name with :index
1604
+ # You can specify the index name with :index
432
1605
 
433
- class Category < ActiveRecord::Base; end
434
- class Advert < ActiveRecord::Base
435
- fields { }
436
- belongs_to :category, index: 'my_index'
437
- end
1606
+ class Category < ActiveRecord::Base; end
1607
+ class Advert < ActiveRecord::Base
1608
+ declare_schema { }
1609
+ belongs_to :category, index: 'my_index'
1610
+ end
438
1611
 
439
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
440
- migrate_up(<<~EOS.strip)
441
- add_column :adverts, :category_id, :integer, limit: 8, null: false
1612
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1613
+ migrate_up(<<~EOS.strip)
1614
+ add_column :adverts, :category_id, :integer, limit: 8, null: false
442
1615
 
443
- add_index :adverts, [:category_id], name: 'my_index'
1616
+ add_index :adverts, [:category_id], name: 'my_index'
444
1617
 
445
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1618
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
446
1619
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
447
1620
  EOS
448
- )
1621
+ )
449
1622
 
450
- Advert.field_specs.delete(:category_id)
451
- Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
1623
+ Advert.field_specs.delete(:category_id)
1624
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["category_id"] }
452
1625
 
453
- ### Timestamps and Optimimistic Locking
1626
+ ### Timestamps and Optimimistic Locking
454
1627
 
455
- # `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
456
- # Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
1628
+ # `updated_at` and `created_at` can be declared with the shorthand `timestamps`.
1629
+ # Similarly, `lock_version` can be declared with the "shorthand" `optimimistic_lock`.
457
1630
 
458
- class Advert < ActiveRecord::Base
459
- fields do
460
- timestamps
461
- optimistic_lock
1631
+ class Advert < ActiveRecord::Base
1632
+ declare_schema do
1633
+ timestamps
1634
+ optimistic_lock
1635
+ end
462
1636
  end
463
- end
464
-
465
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
466
- migrate_up(<<~EOS.strip)
467
- add_column :adverts, :created_at, :datetime, null: true
468
- add_column :adverts, :updated_at, :datetime, null: true
469
- add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
470
-
471
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
472
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
473
- EOS
474
- .and migrate_down(<<~EOS.strip)
475
- remove_column :adverts, :created_at
476
- remove_column :adverts, :updated_at
477
- remove_column :adverts, :lock_version
478
1637
 
479
- #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
1638
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1639
+ migrate_up(<<~EOS.strip)
1640
+ add_column :adverts, :created_at, :datetime, null: true
1641
+ add_column :adverts, :updated_at, :datetime, null: true
1642
+ add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
1643
+
1644
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1645
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
1646
+ EOS
1647
+ .and migrate_down(<<~EOS.strip)
1648
+ remove_column :adverts, :created_at
1649
+ remove_column :adverts, :updated_at
1650
+ remove_column :adverts, :lock_version
1651
+
1652
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
480
1653
  "remove_foreign_key(\"adverts\", name: \"on_c_id\")" if defined?(Mysql2)}
481
1654
  EOS
482
- )
1655
+ )
483
1656
 
484
- Advert.field_specs.delete(:updated_at)
485
- Advert.field_specs.delete(:created_at)
486
- Advert.field_specs.delete(:lock_version)
1657
+ Advert.field_specs.delete(:updated_at)
1658
+ Advert.field_specs.delete(:created_at)
1659
+ Advert.field_specs.delete(:lock_version)
487
1660
 
488
- ### Indices
1661
+ ### Indices
489
1662
 
490
- # You can add an index to a field definition
1663
+ # You can add an index to a field definition
491
1664
 
492
- class Advert < ActiveRecord::Base
493
- fields do
494
- title :string, index: true, limit: 250, null: true
1665
+ class Advert < ActiveRecord::Base
1666
+ declare_schema do
1667
+ string :title, index: true, limit: 250, null: true
1668
+ end
495
1669
  end
496
- end
497
1670
 
498
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
499
- migrate_up(<<~EOS.strip)
500
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1671
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1672
+ migrate_up(<<~EOS.strip)
1673
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
501
1674
 
502
- add_index :adverts, [:title], name: 'on_title'
1675
+ add_index :adverts, [:title], name: 'on_title'
503
1676
 
504
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1677
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
505
1678
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
506
1679
  EOS
507
- )
1680
+ )
508
1681
 
509
- Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
1682
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
510
1683
 
511
- # You can ask for a unique index
1684
+ # You can ask for a unique index
512
1685
 
513
- class Advert < ActiveRecord::Base
514
- fields do
515
- title :string, index: true, unique: true, null: true, limit: 250
1686
+ class Advert < ActiveRecord::Base
1687
+ declare_schema do
1688
+ string :title, index: true, unique: true, null: true, limit: 250
1689
+ end
516
1690
  end
517
- end
518
1691
 
519
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
520
- migrate_up(<<~EOS.strip)
521
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1692
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1693
+ migrate_up(<<~EOS.strip)
1694
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
522
1695
 
523
- add_index :adverts, [:title], unique: true, name: 'on_title'
1696
+ add_index :adverts, [:title], unique: true, name: 'on_title'
524
1697
 
525
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1698
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
526
1699
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
527
1700
  EOS
528
- )
1701
+ )
529
1702
 
530
- Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
1703
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
531
1704
 
532
- # You can specify the name for the index
1705
+ # You can specify the name for the index
533
1706
 
534
- class Advert < ActiveRecord::Base
535
- fields do
536
- title :string, index: 'my_index', limit: 250, null: true
1707
+ class Advert < ActiveRecord::Base
1708
+ declare_schema do
1709
+ string :title, index: 'my_index', limit: 250, null: true
1710
+ end
537
1711
  end
538
- end
539
1712
 
540
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
541
- migrate_up(<<~EOS.strip)
542
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1713
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1714
+ migrate_up(<<~EOS.strip)
1715
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
543
1716
 
544
- add_index :adverts, [:title], name: 'my_index'
1717
+ add_index :adverts, [:title], name: 'my_index'
545
1718
 
546
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1719
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
547
1720
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
548
1721
  EOS
549
- )
1722
+ )
550
1723
 
551
- Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
1724
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title"] }
552
1725
 
553
- # You can ask for an index outside of the fields block
1726
+ # You can ask for an index outside of the fields block
554
1727
 
555
- class Advert < ActiveRecord::Base
556
- index :title
557
- end
1728
+ class Advert < ActiveRecord::Base
1729
+ index :title
1730
+ end
558
1731
 
559
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
560
- migrate_up(<<~EOS.strip)
561
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1732
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1733
+ migrate_up(<<~EOS.strip)
1734
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
562
1735
 
563
- add_index :adverts, [:title], name: 'on_title'
1736
+ add_index :adverts, [:title], name: 'on_title'
564
1737
 
565
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1738
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
566
1739
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
567
1740
  EOS
568
- )
1741
+ )
569
1742
 
570
- Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
1743
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
571
1744
 
572
- # The available options for the index function are `:unique` and `:name`
1745
+ # The available options for the index function are `:unique` and `:name`
573
1746
 
574
- class Advert < ActiveRecord::Base
575
- index :title, unique: true, name: 'my_index'
576
- end
1747
+ class Advert < ActiveRecord::Base
1748
+ index :title, unique: true, name: 'my_index'
1749
+ end
577
1750
 
578
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
579
- migrate_up(<<~EOS.strip)
580
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1751
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1752
+ migrate_up(<<~EOS.strip)
1753
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
581
1754
 
582
- add_index :adverts, [:title], unique: true, name: 'my_index'
1755
+ add_index :adverts, [:title], unique: true, name: 'my_index'
583
1756
 
584
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1757
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
585
1758
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
586
1759
  EOS
587
- )
1760
+ )
588
1761
 
589
- Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
1762
+ Advert.index_definitions.delete_if { |spec| spec.fields == ["title"] }
590
1763
 
591
- # You can create an index on more than one field
1764
+ # You can create an index on more than one field
592
1765
 
593
- class Advert < ActiveRecord::Base
594
- index [:title, :category_id]
595
- end
1766
+ class Advert < ActiveRecord::Base
1767
+ index [:title, :category_id]
1768
+ end
596
1769
 
597
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
598
- migrate_up(<<~EOS.strip)
599
- add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
1770
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1771
+ migrate_up(<<~EOS.strip)
1772
+ add_column :adverts, :title, :string, limit: 250, null: true#{charset_and_collation}
600
1773
 
601
- add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
1774
+ add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
602
1775
 
603
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1776
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
604
1777
  "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
605
1778
  EOS
606
- )
1779
+ )
607
1780
 
608
- Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
1781
+ Advert.index_definitions.delete_if { |spec| spec.fields==["title", "category_id"] }
609
1782
 
610
- # Finally, you can specify that the migration generator should completely ignore an
611
- # index by passing its name to ignore_index in the model.
612
- # This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
1783
+ # Finally, you can specify that the migration generator should completely ignore an
1784
+ # index by passing its name to ignore_index in the model.
1785
+ # This is helpful for preserving indices that can't be automatically generated, such as prefix indices in MySQL.
613
1786
 
614
- ### Rename a table
1787
+ ### Rename a table
615
1788
 
616
- # The migration generator respects the `set_table_name` declaration, although as before, we need to explicitly tell the generator that we want a rename rather than a create and a drop.
1789
+ # The migration generator respects the `set_table_name` declaration, although as before, we need to explicitly tell the generator that we want a rename rather than a create and a drop.
617
1790
 
618
- class Advert < ActiveRecord::Base
619
- self.table_name = "ads"
620
- fields do
621
- title :string, limit: 250, null: true
622
- body :text, null: true
1791
+ class Advert < ActiveRecord::Base
1792
+ self.table_name = "ads"
1793
+ declare_schema do
1794
+ string :title, limit: 250, null: true
1795
+ text :body, null: true
1796
+ end
623
1797
  end
624
- end
625
-
626
- Advert.connection.schema_cache.clear!
627
- Advert.reset_column_information
628
1798
 
629
- expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
630
- migrate_up(<<~EOS.strip)
631
- rename_table :adverts, :ads
632
-
633
- add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
634
- add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
1799
+ Advert.connection.schema_cache.clear!
1800
+ Advert.reset_column_information
635
1801
 
636
- #{if defined?(SQLite3)
637
- "add_index :ads, [:id], unique: true, name: 'PRIMARY'\n"
638
- elsif defined?(Mysql2)
639
- "execute \"ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
640
- "add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
641
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")"
642
- end}
643
- EOS
644
- .and migrate_down(<<~EOS.strip)
645
- remove_column :ads, :title
646
- remove_column :ads, :body
647
-
648
- rename_table :ads, :adverts
649
-
650
- #{if defined?(SQLite3)
651
- "add_index :adverts, [:id], unique: true, name: 'PRIMARY'\n"
652
- elsif defined?(Mysql2)
653
- "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
654
- "remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
655
- "remove_foreign_key(\"adverts\", name: \"on_c_id\")"
656
- end}
1802
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "ads")).to(
1803
+ migrate_up(<<~EOS.strip)
1804
+ rename_table :adverts, :ads
1805
+
1806
+ add_column :ads, :title, :string, limit: 250, null: true#{charset_and_collation}
1807
+ add_column :ads, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
1808
+
1809
+ #{if defined?(SQLite3)
1810
+ "add_index :ads, [:id], unique: true, name: 'PRIMARY'\n"
1811
+ elsif defined?(Mysql2)
1812
+ "execute \"ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
1813
+ "add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
1814
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")"
1815
+ end}
1816
+ EOS
1817
+ .and migrate_down(<<~EOS.strip)
1818
+ remove_column :ads, :title
1819
+ remove_column :ads, :body
1820
+
1821
+ rename_table :ads, :adverts
1822
+
1823
+ #{if defined?(SQLite3)
1824
+ "add_index :adverts, [:id], unique: true, name: 'PRIMARY'\n"
1825
+ elsif defined?(Mysql2)
1826
+ "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
1827
+ "remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
1828
+ "remove_foreign_key(\"adverts\", name: \"on_c_id\")"
1829
+ end}
657
1830
  EOS
658
- )
1831
+ )
659
1832
 
660
- # Set the table name back to what it should be and confirm we're in sync:
1833
+ # Set the table name back to what it should be and confirm we're in sync:
661
1834
 
662
- nuke_model_class(Advert)
1835
+ nuke_model_class(Advert)
663
1836
 
664
- class Advert < ActiveRecord::Base
665
- self.table_name = "adverts"
666
- end
1837
+ class Advert < ActiveRecord::Base
1838
+ self.table_name = "adverts"
1839
+ end
667
1840
 
668
- expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
1841
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
669
1842
 
670
- ### Rename a table
1843
+ ### Rename a table
671
1844
 
672
- # As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
1845
+ # As with renaming columns, we have to tell the migration generator about the rename. Here we create a new class 'Advertisement', and tell ActiveRecord to forget about the Advert class. This requires code that shouldn't be shown to impressionable children.
673
1846
 
674
- nuke_model_class(Advert)
1847
+ nuke_model_class(Advert)
675
1848
 
676
- class Advertisement < ActiveRecord::Base
677
- fields do
678
- title :string, limit: 250, null: true
679
- body :text, null: true
1849
+ class Advertisement < ActiveRecord::Base
1850
+ declare_schema do
1851
+ string :title, limit: 250, null: true
1852
+ text :body, null: true
1853
+ end
680
1854
  end
681
- end
682
-
683
- expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
684
- migrate_up(<<~EOS.strip)
685
- rename_table :adverts, :advertisements
686
-
687
- add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
688
- add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
689
- remove_column :advertisements, :name
690
1855
 
691
- #{if defined?(SQLite3)
692
- "add_index :advertisements, [:id], unique: true, name: 'PRIMARY'"
693
- elsif defined?(Mysql2)
694
- "execute \"ALTER TABLE advertisements DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
695
- end}
1856
+ expect(Generators::DeclareSchema::Migration::Migrator.run("adverts" => "advertisements")).to(
1857
+ migrate_up(<<~EOS.strip)
1858
+ rename_table :adverts, :advertisements
1859
+
1860
+ add_column :advertisements, :title, :string, limit: 250, null: true#{charset_and_collation}
1861
+ add_column :advertisements, :body, :text#{', limit: 4294967295' if defined?(Mysql2)}, null: true#{charset_and_collation}
1862
+ remove_column :advertisements, :name
1863
+
1864
+ #{if defined?(SQLite3)
1865
+ "add_index :advertisements, [:id], unique: true, name: 'PRIMARY'"
1866
+ elsif defined?(Mysql2)
1867
+ "execute \"ALTER TABLE advertisements DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
1868
+ end}
1869
+ EOS
1870
+ .and migrate_down(<<~EOS.strip)
1871
+ remove_column :advertisements, :title
1872
+ remove_column :advertisements, :body
1873
+ add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
1874
+
1875
+ rename_table :advertisements, :adverts
1876
+
1877
+ #{if defined?(SQLite3)
1878
+ "add_index :adverts, [:id], unique: true, name: 'PRIMARY'"
1879
+ elsif defined?(Mysql2)
1880
+ "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
1881
+ end}
696
1882
  EOS
697
- .and migrate_down(<<~EOS.strip)
698
- remove_column :advertisements, :title
699
- remove_column :advertisements, :body
700
- add_column :adverts, :name, :string, limit: 250, null: true#{charset_and_collation}
701
-
702
- rename_table :advertisements, :adverts
703
-
704
- #{if defined?(SQLite3)
705
- "add_index :adverts, [:id], unique: true, name: 'PRIMARY'"
706
- elsif defined?(Mysql2)
707
- "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\""
708
- end}
709
- EOS
710
- )
1883
+ )
711
1884
 
712
- ### Drop a table
1885
+ ### Drop a table
713
1886
 
714
- nuke_model_class(Advertisement)
1887
+ nuke_model_class(Advertisement)
715
1888
 
716
- # If you delete a model, the migration generator will create a `drop_table` migration.
1889
+ # If you delete a model, the migration generator will create a `drop_table` migration.
717
1890
 
718
- # Dropping tables is where the automatic down-migration really comes in handy:
1891
+ # Dropping tables is where the automatic down-migration really comes in handy:
719
1892
 
720
- expect(Generators::DeclareSchema::Migration::Migrator.run).to(
721
- migrate_up(<<~EOS.strip)
722
- drop_table :adverts
723
- EOS
724
- .and migrate_down(<<~EOS.strip)
725
- create_table "adverts"#{table_options}, force: :cascade do |t|
726
- t.string "name", limit: 250#{charset_and_collation}
727
- end
1893
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to(
1894
+ migrate_up(<<~EOS.strip)
1895
+ drop_table :adverts
1896
+ EOS
1897
+ .and migrate_down(<<~EOS.strip)
1898
+ create_table "adverts"#{table_options}, force: :cascade do |t|
1899
+ t.string "name", limit: 250#{charset_and_collation}
1900
+ end
728
1901
  EOS
729
- )
1902
+ )
730
1903
 
731
- ## STI
1904
+ ## STI
732
1905
 
733
- ### Adding an STI subclass
1906
+ ### Adding an STI subclass
734
1907
 
735
- # Adding a subclass or two should introduce the 'type' column and no other changes
1908
+ # Adding a subclass or two should introduce the 'type' column and no other changes
736
1909
 
737
- class Advert < ActiveRecord::Base
738
- fields do
739
- body :text, null: true
740
- title :string, default: "Untitled", limit: 250, null: true
1910
+ class Advert < ActiveRecord::Base
1911
+ declare_schema do
1912
+ text :body, null: true
1913
+ string :title, default: "Untitled", limit: 250, null: true
1914
+ end
741
1915
  end
742
- end
743
- up = Generators::DeclareSchema::Migration::Migrator.run.first
744
- ActiveRecord::Migration.class_eval(up)
1916
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
1917
+ ActiveRecord::Migration.class_eval(up)
745
1918
 
746
- class FancyAdvert < Advert
747
- end
748
- class SuperFancyAdvert < FancyAdvert
749
- end
1919
+ class FancyAdvert < Advert
1920
+ end
1921
+ class SuperFancyAdvert < FancyAdvert
1922
+ end
750
1923
 
751
- up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
752
- expect(migrations).to(
753
- migrate_up(<<~EOS.strip)
754
- add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
1924
+ up, _ = Generators::DeclareSchema::Migration::Migrator.run do |migrations|
1925
+ expect(migrations).to(
1926
+ migrate_up(<<~EOS.strip)
1927
+ add_column :adverts, :type, :string, limit: 250, null: true#{charset_and_collation}
755
1928
 
756
- add_index :adverts, [:type], name: 'on_type'
757
- EOS
758
- .and migrate_down(<<~EOS.strip)
759
- remove_column :adverts, :type
1929
+ add_index :adverts, [:type], name: 'on_type'
1930
+ EOS
1931
+ .and migrate_down(<<~EOS.strip)
1932
+ remove_column :adverts, :type
760
1933
 
761
- remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
1934
+ remove_index :adverts, name: :on_type rescue ActiveRecord::StatementInvalid
762
1935
  EOS
763
- )
764
- end
1936
+ )
1937
+ end
765
1938
 
766
- Advert.field_specs.delete(:type)
767
- nuke_model_class(SuperFancyAdvert)
768
- nuke_model_class(FancyAdvert)
769
- Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
1939
+ Advert.field_specs.delete(:type)
1940
+ nuke_model_class(SuperFancyAdvert)
1941
+ nuke_model_class(FancyAdvert)
1942
+ Advert.index_definitions.delete_if { |spec| spec.fields==["type"] }
770
1943
 
771
- ## Coping with multiple changes
1944
+ ## Coping with multiple changes
772
1945
 
773
- # The migration generator is designed to create complete migrations even if many changes to the models have taken place.
1946
+ # The migration generator is designed to create complete migrations even if many changes to the models have taken place.
774
1947
 
775
- # First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
1948
+ # First let's confirm we're in a known state. One model, 'Advert', with a string 'title' and text 'body':
776
1949
 
777
- ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
778
- Advert.connection.schema_cache.clear!
779
- Advert.reset_column_information
1950
+ ActiveRecord::Migration.class_eval up.gsub(/.*type.*/, '')
1951
+ Advert.connection.schema_cache.clear!
1952
+ Advert.reset_column_information
780
1953
 
781
- expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
782
- to eq(["adverts"])
783
- expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
784
- expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
1954
+ expect(Advert.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).
1955
+ to eq(["adverts"])
1956
+ expect(Advert.columns.map(&:name).sort).to eq(["body", "id", "title"])
1957
+ expect(Generators::DeclareSchema::Migration::Migrator.run).to eq(["", ""])
785
1958
 
786
1959
 
787
- ### Rename a column and change the default
1960
+ ### Rename a column and change the default
788
1961
 
789
- Advert.field_specs.clear
1962
+ Advert.field_specs.clear
790
1963
 
791
- class Advert < ActiveRecord::Base
792
- fields do
793
- name :string, default: "No Name", limit: 250, null: true
794
- body :text, null: true
1964
+ class Advert < ActiveRecord::Base
1965
+ declare_schema do
1966
+ string :name, default: "No Name", limit: 250, null: true
1967
+ text :body, null: true
1968
+ end
795
1969
  end
796
- end
797
1970
 
798
- expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
799
- migrate_up(<<~EOS.strip)
800
- rename_column :adverts, :title, :name
801
- change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
802
- EOS
803
- .and migrate_down(<<~EOS.strip)
804
- rename_column :adverts, :name, :title
805
- change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
1971
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { title: :name })).to(
1972
+ migrate_up(<<~EOS.strip)
1973
+ rename_column :adverts, :title, :name
1974
+ change_column :adverts, :name, :string, limit: 250, null: true, default: "No Name"#{charset_and_collation}
1975
+ EOS
1976
+ .and migrate_down(<<~EOS.strip)
1977
+ rename_column :adverts, :name, :title
1978
+ change_column :adverts, :title, :string, limit: 250, null: true, default: "Untitled"#{charset_and_collation}
806
1979
  EOS
807
- )
1980
+ )
808
1981
 
809
- ### Rename a table and add a column
1982
+ ### Rename a table and add a column
810
1983
 
811
- nuke_model_class(Advert)
812
- class Ad < ActiveRecord::Base
813
- fields do
814
- title :string, default: "Untitled", limit: 250
815
- body :text, null: true
816
- created_at :datetime
1984
+ nuke_model_class(Advert)
1985
+ class Ad < ActiveRecord::Base
1986
+ declare_schema do
1987
+ string :title, default: "Untitled", limit: 250
1988
+ text :body, null: true
1989
+ datetime :created_at
1990
+ end
817
1991
  end
818
- end
819
1992
 
820
- expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
821
- migrate_up(<<~EOS.strip)
822
- rename_table :adverts, :ads
1993
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads)).to(
1994
+ migrate_up(<<~EOS.strip)
1995
+ rename_table :adverts, :ads
823
1996
 
824
- add_column :ads, :created_at, :datetime, null: false
825
- change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
1997
+ add_column :ads, :created_at, :datetime, null: false
1998
+ change_column :ads, :title, :string, limit: 250, null: false, default: \"Untitled\"#{charset_and_collation}
826
1999
 
827
- #{if defined?(SQLite3)
828
- "add_index :ads, [:id], unique: true, name: 'PRIMARY'"
829
- elsif defined?(Mysql2)
830
- 'execute "ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)"'
831
- end}
2000
+ #{if defined?(SQLite3)
2001
+ "add_index :ads, [:id], unique: true, name: 'PRIMARY'"
2002
+ elsif defined?(Mysql2)
2003
+ 'execute "ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)"'
2004
+ end}
832
2005
  EOS
833
- )
2006
+ )
834
2007
 
835
- class Advert < ActiveRecord::Base
836
- fields do
837
- body :text, null: true
838
- title :string, default: "Untitled", limit: 250, null: true
2008
+ class Advert < ActiveRecord::Base
2009
+ declare_schema do
2010
+ text :body, null: true
2011
+ string :title, default: "Untitled", limit: 250, null: true
2012
+ end
839
2013
  end
840
- end
841
2014
 
842
- ## Legacy Keys
2015
+ ## Legacy Keys
843
2016
 
844
- # DeclareSchema has some support for legacy keys.
2017
+ # DeclareSchema has some support for legacy keys.
845
2018
 
846
- nuke_model_class(Ad)
2019
+ nuke_model_class(Ad)
847
2020
 
848
- class Advert < ActiveRecord::Base
849
- fields do
850
- body :text, null: true
2021
+ class Advert < ActiveRecord::Base
2022
+ declare_schema do
2023
+ text :body, null: true
2024
+ end
2025
+ self.primary_key = "advert_id"
851
2026
  end
852
- self.primary_key = "advert_id"
853
- end
854
2027
 
855
- expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
856
- migrate_up(<<~EOS.strip)
857
- rename_column :adverts, :id, :advert_id
2028
+ expect(Generators::DeclareSchema::Migration::Migrator.run(adverts: { id: :advert_id })).to(
2029
+ migrate_up(<<~EOS.strip)
2030
+ rename_column :adverts, :id, :advert_id
858
2031
 
859
- #{if defined?(SQLite3)
860
- "add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY'"
861
- elsif defined?(Mysql2)
862
- 'execute "ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (advert_id)"'
863
- end}
2032
+ #{if defined?(SQLite3)
2033
+ "add_index :adverts, [:advert_id], unique: true, name: 'PRIMARY'"
2034
+ elsif defined?(Mysql2)
2035
+ 'execute "ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (advert_id)"'
2036
+ end}
864
2037
  EOS
865
- )
2038
+ )
866
2039
 
867
- nuke_model_class(Advert)
868
- ActiveRecord::Base.connection.execute("drop table `adverts`;")
2040
+ nuke_model_class(Advert)
2041
+ ActiveRecord::Base.connection.execute("drop table `adverts`;")
869
2042
 
870
- ## DSL
2043
+ ## DSL
871
2044
 
872
- # The DSL allows lambdas and constants
2045
+ # The DSL allows lambdas and constants
873
2046
 
874
- class User < ActiveRecord::Base
875
- fields do
876
- company :string, limit: 250, ruby_default: -> { "BigCorp" }
2047
+ class User < ActiveRecord::Base
2048
+ declare_schema do
2049
+ string :company, limit: 250, ruby_default: -> { "BigCorp" }
2050
+ end
877
2051
  end
878
- end
879
- expect(User.field_specs.keys).to eq(['company'])
880
- expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
2052
+ expect(User.field_specs.keys).to eq(['company'])
2053
+ expect(User.field_specs['company'].options[:ruby_default]&.call).to eq("BigCorp")
881
2054
 
882
- ## validates
2055
+ ## validates
883
2056
 
884
- # DeclareSchema can accept a validates hash in the field options.
2057
+ # DeclareSchema can accept a validates hash in the field options.
885
2058
 
886
- class Ad < ActiveRecord::Base
887
- class << self
888
- def validates(field_name, options)
2059
+ class Ad < ActiveRecord::Base
2060
+ class << self
2061
+ def validates(field_name, options)
2062
+ end
889
2063
  end
890
2064
  end
891
- end
892
- expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
893
- class Ad < ActiveRecord::Base
894
- fields do
895
- company :string, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
2065
+ expect(Ad).to receive(:validates).with(:company, presence: true, uniqueness: { case_sensitive: false })
2066
+ class Ad < ActiveRecord::Base
2067
+ declare_schema do
2068
+ string :company, limit: 250, index: true, unique: true, validates: { presence: true, uniqueness: { case_sensitive: false } }
2069
+ end
2070
+ self.primary_key = "advert_id"
896
2071
  end
897
- self.primary_key = "advert_id"
2072
+ up, _down = Generators::DeclareSchema::Migration::Migrator.run
2073
+ ActiveRecord::Migration.class_eval(up)
2074
+ expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
898
2075
  end
899
- up, _down = Generators::DeclareSchema::Migration::Migrator.run
900
- ActiveRecord::Migration.class_eval(up)
901
- expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
902
- end
903
2076
 
904
- describe 'serialize' do
905
- before do
906
- class Ad < ActiveRecord::Base
907
- @serialize_args = []
2077
+ describe 'serialize' do
2078
+ before do
2079
+ class Ad < ActiveRecord::Base
2080
+ @serialize_args = []
908
2081
 
909
- class << self
910
- attr_reader :serialize_args
2082
+ class << self
2083
+ attr_reader :serialize_args
911
2084
 
912
- def serialize(*args)
913
- @serialize_args << args
2085
+ def serialize(*args)
2086
+ @serialize_args << args
2087
+ end
914
2088
  end
915
2089
  end
916
2090
  end
917
- end
918
2091
 
919
- describe 'untyped' do
920
- it 'allows serialize: true' do
921
- class Ad < ActiveRecord::Base
922
- fields do
923
- allow_list :text, limit: 0xFFFF, serialize: true
2092
+ describe 'untyped' do
2093
+ it 'allows serialize: true' do
2094
+ class Ad < ActiveRecord::Base
2095
+ declare_schema do
2096
+ text :allow_list, limit: 0xFFFF, serialize: true
2097
+ end
924
2098
  end
925
- end
926
2099
 
927
- expect(Ad.serialize_args).to eq([[:allow_list]])
928
- end
2100
+ expect(Ad.serialize_args).to eq([[:allow_list]])
2101
+ end
929
2102
 
930
- it 'converts defaults with .to_yaml' do
931
- class Ad < ActiveRecord::Base
932
- fields do
933
- allow_list :string, limit: 250, serialize: true, null: true, default: []
934
- allow_hash :string, limit: 250, serialize: true, null: true, default: {}
935
- allow_string :string, limit: 250, serialize: true, null: true, default: ['abc']
936
- allow_null :string, limit: 250, serialize: true, null: true, default: nil
2103
+ it 'converts defaults with .to_yaml' do
2104
+ class Ad < ActiveRecord::Base
2105
+ declare_schema do
2106
+ string :allow_list, limit: 250, serialize: true, null: true, default: []
2107
+ string :allow_hash, limit: 250, serialize: true, null: true, default: {}
2108
+ string :allow_string, limit: 250, serialize: true, null: true, default: ['abc']
2109
+ string :allow_null, limit: 250, serialize: true, null: true, default: nil
2110
+ end
937
2111
  end
938
- end
939
2112
 
940
- expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
941
- expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
942
- expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
943
- expect(Ad.field_specs['allow_null'].default).to eq(nil)
2113
+ expect(Ad.field_specs['allow_list'].default).to eq("--- []\n")
2114
+ expect(Ad.field_specs['allow_hash'].default).to eq("--- {}\n")
2115
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
2116
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
2117
+ end
944
2118
  end
945
- end
946
2119
 
947
- describe 'Array' do
948
- it 'allows serialize: Array' do
949
- class Ad < ActiveRecord::Base
950
- fields do
951
- allow_list :string, limit: 250, serialize: Array, null: true
2120
+ describe 'Array' do
2121
+ it 'allows serialize: Array' do
2122
+ class Ad < ActiveRecord::Base
2123
+ declare_schema do
2124
+ string :allow_list, limit: 250, serialize: Array, null: true
2125
+ end
952
2126
  end
953
- end
954
2127
 
955
- expect(Ad.serialize_args).to eq([[:allow_list, Array]])
956
- end
2128
+ expect(Ad.serialize_args).to eq([[:allow_list, Array]])
2129
+ end
957
2130
 
958
- it 'allows Array defaults' do
959
- class Ad < ActiveRecord::Base
960
- fields do
961
- allow_list :string, limit: 250, serialize: Array, null: true, default: [2]
962
- allow_string :string, limit: 250, serialize: Array, null: true, default: ['abc']
963
- allow_empty :string, limit: 250, serialize: Array, null: true, default: []
964
- allow_null :string, limit: 250, serialize: Array, null: true, default: nil
2131
+ it 'allows Array defaults' do
2132
+ class Ad < ActiveRecord::Base
2133
+ declare_schema do
2134
+ string :allow_list, limit: 250, serialize: Array, null: true, default: [2]
2135
+ string :allow_string, limit: 250, serialize: Array, null: true, default: ['abc']
2136
+ string :allow_empty, limit: 250, serialize: Array, null: true, default: []
2137
+ string :allow_null, limit: 250, serialize: Array, null: true, default: nil
2138
+ end
965
2139
  end
966
- end
967
2140
 
968
- expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
969
- expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
970
- expect(Ad.field_specs['allow_empty'].default).to eq(nil)
971
- expect(Ad.field_specs['allow_null'].default).to eq(nil)
2141
+ expect(Ad.field_specs['allow_list'].default).to eq("---\n- 2\n")
2142
+ expect(Ad.field_specs['allow_string'].default).to eq("---\n- abc\n")
2143
+ expect(Ad.field_specs['allow_empty'].default).to eq(nil)
2144
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
2145
+ end
972
2146
  end
973
- end
974
2147
 
975
- describe 'Hash' do
976
- it 'allows serialize: Hash' do
977
- class Ad < ActiveRecord::Base
978
- fields do
979
- allow_list :string, limit: 250, serialize: Hash, null: true
2148
+ describe 'Hash' do
2149
+ it 'allows serialize: Hash' do
2150
+ class Ad < ActiveRecord::Base
2151
+ declare_schema do
2152
+ string :allow_list, limit: 250, serialize: Hash, null: true
2153
+ end
980
2154
  end
981
- end
982
2155
 
983
- expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
984
- end
2156
+ expect(Ad.serialize_args).to eq([[:allow_list, Hash]])
2157
+ end
985
2158
 
986
- it 'allows Hash defaults' do
987
- class Ad < ActiveRecord::Base
988
- fields do
989
- allow_loc :string, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
990
- allow_hash :string, limit: 250, serialize: Hash, null: true, default: {}
991
- allow_null :string, limit: 250, serialize: Hash, null: true, default: nil
2159
+ it 'allows Hash defaults' do
2160
+ class Ad < ActiveRecord::Base
2161
+ declare_schema do
2162
+ string :allow_loc, limit: 250, serialize: Hash, null: true, default: { 'state' => 'CA' }
2163
+ string :allow_hash, limit: 250, serialize: Hash, null: true, default: {}
2164
+ string :allow_null, limit: 250, serialize: Hash, null: true, default: nil
2165
+ end
992
2166
  end
993
- end
994
2167
 
995
- expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
996
- expect(Ad.field_specs['allow_hash'].default).to eq(nil)
997
- expect(Ad.field_specs['allow_null'].default).to eq(nil)
2168
+ expect(Ad.field_specs['allow_loc'].default).to eq("---\nstate: CA\n")
2169
+ expect(Ad.field_specs['allow_hash'].default).to eq(nil)
2170
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
2171
+ end
998
2172
  end
999
- end
1000
2173
 
1001
- describe 'JSON' do
1002
- it 'allows serialize: JSON' do
1003
- class Ad < ActiveRecord::Base
1004
- fields do
1005
- allow_list :string, limit: 250, serialize: JSON
2174
+ describe 'JSON' do
2175
+ it 'allows serialize: JSON' do
2176
+ class Ad < ActiveRecord::Base
2177
+ declare_schema do
2178
+ string :allow_list, limit: 250, serialize: JSON
2179
+ end
1006
2180
  end
1007
- end
1008
2181
 
1009
- expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
1010
- end
2182
+ expect(Ad.serialize_args).to eq([[:allow_list, JSON]])
2183
+ end
1011
2184
 
1012
- it 'allows JSON defaults' do
1013
- class Ad < ActiveRecord::Base
1014
- fields do
1015
- allow_hash :string, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
1016
- allow_empty_array :string, limit: 250, serialize: JSON, null: true, default: []
1017
- allow_empty_hash :string, limit: 250, serialize: JSON, null: true, default: {}
1018
- allow_null :string, limit: 250, serialize: JSON, null: true, default: nil
2185
+ it 'allows JSON defaults' do
2186
+ class Ad < ActiveRecord::Base
2187
+ declare_schema do
2188
+ string :allow_hash, limit: 250, serialize: JSON, null: true, default: { 'state' => 'CA' }
2189
+ string :allow_empty_array, limit: 250, serialize: JSON, null: true, default: []
2190
+ string :allow_empty_hash, limit: 250, serialize: JSON, null: true, default: {}
2191
+ string :allow_null, limit: 250, serialize: JSON, null: true, default: nil
2192
+ end
1019
2193
  end
1020
- end
1021
2194
 
1022
- expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
1023
- expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
1024
- expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
1025
- expect(Ad.field_specs['allow_null'].default).to eq(nil)
2195
+ expect(Ad.field_specs['allow_hash'].default).to eq("{\"state\":\"CA\"}")
2196
+ expect(Ad.field_specs['allow_empty_array'].default).to eq("[]")
2197
+ expect(Ad.field_specs['allow_empty_hash'].default).to eq("{}")
2198
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
2199
+ end
1026
2200
  end
1027
- end
1028
2201
 
1029
- class ValueClass
1030
- delegate :present?, :inspect, to: :@value
2202
+ class ValueClass
2203
+ delegate :present?, :inspect, to: :@value
1031
2204
 
1032
- def initialize(value)
1033
- @value = value
1034
- end
2205
+ def initialize(value)
2206
+ @value = value
2207
+ end
1035
2208
 
1036
- class << self
1037
- def dump(object)
1038
- if object&.present?
1039
- object.inspect
2209
+ class << self
2210
+ def dump(object)
2211
+ if object&.present?
2212
+ object.inspect
2213
+ end
1040
2214
  end
1041
- end
1042
2215
 
1043
- def load(serialized)
1044
- if serialized
1045
- raise 'not used ???'
2216
+ def load(serialized)
2217
+ if serialized
2218
+ raise 'not used ???'
2219
+ end
1046
2220
  end
1047
2221
  end
1048
2222
  end
1049
- end
1050
2223
 
1051
- describe 'custom coder' do
1052
- it 'allows serialize: ValueClass' do
1053
- class Ad < ActiveRecord::Base
1054
- fields do
1055
- allow_list :string, limit: 250, serialize: ValueClass
2224
+ describe 'custom coder' do
2225
+ it 'allows serialize: ValueClass' do
2226
+ class Ad < ActiveRecord::Base
2227
+ declare_schema do
2228
+ string :allow_list, limit: 250, serialize: ValueClass
2229
+ end
1056
2230
  end
1057
- end
1058
2231
 
1059
- expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
1060
- end
2232
+ expect(Ad.serialize_args).to eq([[:allow_list, ValueClass]])
2233
+ end
1061
2234
 
1062
- it 'allows ValueClass defaults' do
1063
- class Ad < ActiveRecord::Base
1064
- fields do
1065
- allow_hash :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
1066
- allow_empty_array :string, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
1067
- allow_null :string, limit: 250, serialize: ValueClass, null: true, default: nil
2235
+ it 'allows ValueClass defaults' do
2236
+ class Ad < ActiveRecord::Base
2237
+ declare_schema do
2238
+ string :allow_hash, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([2])
2239
+ string :allow_empty_array, limit: 250, serialize: ValueClass, null: true, default: ValueClass.new([])
2240
+ string :allow_null, limit: 250, serialize: ValueClass, null: true, default: nil
2241
+ end
1068
2242
  end
1069
- end
1070
2243
 
1071
- expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
1072
- expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
1073
- expect(Ad.field_specs['allow_null'].default).to eq(nil)
2244
+ expect(Ad.field_specs['allow_hash'].default).to eq("[2]")
2245
+ expect(Ad.field_specs['allow_empty_array'].default).to eq(nil)
2246
+ expect(Ad.field_specs['allow_null'].default).to eq(nil)
2247
+ end
1074
2248
  end
1075
- end
1076
2249
 
1077
- it 'disallows serialize: with a non-string column type' do
1078
- expect do
1079
- class Ad < ActiveRecord::Base
1080
- fields do
1081
- allow_list :integer, limit: 8, serialize: true
2250
+ it 'disallows serialize: with a non-string column type' do
2251
+ expect do
2252
+ class Ad < ActiveRecord::Base
2253
+ declare_schema do
2254
+ integer :allow_list, limit: 8, serialize: true
2255
+ end
1082
2256
  end
1083
- end
1084
- end.to raise_exception(ArgumentError, /must be :string or :text/)
1085
- end
1086
- end
1087
-
1088
- context "for Rails #{Rails::VERSION::MAJOR}" do
1089
- if Rails::VERSION::MAJOR >= 5
1090
- let(:optional_true) { { optional: true } }
1091
- let(:optional_false) { { optional: false } }
1092
- else
1093
- let(:optional_true) { {} }
1094
- let(:optional_false) { {} }
2257
+ end.to raise_exception(ArgumentError, /must be :string or :text/)
2258
+ end
1095
2259
  end
1096
- let(:optional_flag) { { false => optional_false, true => optional_true } }
1097
2260
 
1098
- describe 'belongs_to' do
1099
- before do
1100
- unless defined?(AdCategory)
1101
- class AdCategory < ActiveRecord::Base
1102
- fields { }
2261
+ context "for Rails #{Rails::VERSION::MAJOR}" do
2262
+ if Rails::VERSION::MAJOR >= 5
2263
+ let(:optional_true) { { optional: true } }
2264
+ let(:optional_false) { { optional: false } }
2265
+ else
2266
+ let(:optional_true) { {} }
2267
+ let(:optional_false) { {} }
2268
+ end
2269
+ let(:optional_flag) { { false => optional_false, true => optional_true } }
2270
+
2271
+ describe 'belongs_to' do
2272
+ before do
2273
+ unless defined?(AdCategory)
2274
+ class AdCategory < ActiveRecord::Base
2275
+ declare_schema { }
2276
+ end
1103
2277
  end
1104
- end
1105
2278
 
1106
- class Advert < ActiveRecord::Base
1107
- fields do
1108
- name :string, limit: 250, null: true
1109
- category_id :integer, limit: 8
1110
- nullable_category_id :integer, limit: 8, null: true
2279
+ class Advert < ActiveRecord::Base
2280
+ declare_schema do
2281
+ string :name, limit: 250, null: true
2282
+ integer :category_id, limit: 8
2283
+ integer :nullable_category_id, limit: 8, null: true
2284
+ end
1111
2285
  end
2286
+ up = Generators::DeclareSchema::Migration::Migrator.run.first
2287
+ ActiveRecord::Migration.class_eval(up)
1112
2288
  end
1113
- up = Generators::DeclareSchema::Migration::Migrator.run.first
1114
- ActiveRecord::Migration.class_eval(up)
1115
- end
1116
-
1117
- it 'passes through optional: when given' do
1118
- class AdvertBelongsTo < ActiveRecord::Base
1119
- self.table_name = 'adverts'
1120
- fields { }
1121
- reset_column_information
1122
- belongs_to :ad_category, optional: true
1123
- end
1124
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1125
- end
1126
2289
 
1127
- describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
1128
- it 'passes through optional: true, null: false' do
2290
+ it 'passes through optional: when given' do
1129
2291
  class AdvertBelongsTo < ActiveRecord::Base
1130
2292
  self.table_name = 'adverts'
1131
- fields { }
2293
+ declare_schema { }
1132
2294
  reset_column_information
1133
- belongs_to :ad_category, optional: true, null: false
2295
+ belongs_to :ad_category, optional: true
1134
2296
  end
1135
2297
  expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
1136
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
1137
2298
  end
1138
2299
 
1139
- it 'passes through optional: false, null: true' do
1140
- class AdvertBelongsTo < ActiveRecord::Base
1141
- self.table_name = 'adverts'
1142
- fields { }
1143
- reset_column_information
1144
- belongs_to :ad_category, optional: false, null: true
2300
+ describe 'contradictory settings' do # contradictory settings are ok--for example, during migration
2301
+ it 'passes through optional: true, null: false' do
2302
+ class AdvertBelongsTo < ActiveRecord::Base
2303
+ self.table_name = 'adverts'
2304
+ declare_schema { }
2305
+ reset_column_information
2306
+ belongs_to :ad_category, optional: true, null: false
2307
+ end
2308
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_true)
2309
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
1145
2310
  end
1146
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
1147
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
1148
- end
1149
- end
1150
2311
 
1151
- [false, true].each do |nullable|
1152
- context "nullable=#{nullable}" do
1153
- it 'infers optional: from null:' do
1154
- eval <<~EOS
1155
- class AdvertBelongsTo < ActiveRecord::Base
1156
- fields { }
1157
- belongs_to :ad_category, null: #{nullable}
1158
- end
1159
- EOS
1160
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1161
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2312
+ it 'passes through optional: false, null: true' do
2313
+ class AdvertBelongsTo < ActiveRecord::Base
2314
+ self.table_name = 'adverts'
2315
+ declare_schema { }
2316
+ reset_column_information
2317
+ belongs_to :ad_category, optional: false, null: true
2318
+ end
2319
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_false)
2320
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
1162
2321
  end
2322
+ end
1163
2323
 
1164
- it 'infers null: from optional:' do
1165
- eval <<~EOS
1166
- class AdvertBelongsTo < ActiveRecord::Base
1167
- fields { }
1168
- belongs_to :ad_category, optional: #{nullable}
1169
- end
1170
- EOS
1171
- expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
1172
- expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2324
+ [false, true].each do |nullable|
2325
+ context "nullable=#{nullable}" do
2326
+ it 'infers optional: from null:' do
2327
+ eval <<~EOS
2328
+ class AdvertBelongsTo < ActiveRecord::Base
2329
+ declare_schema { }
2330
+ belongs_to :ad_category, null: #{nullable}
2331
+ end
2332
+ EOS
2333
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2334
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2335
+ end
2336
+
2337
+ it 'infers null: from optional:' do
2338
+ eval <<~EOS
2339
+ class AdvertBelongsTo < ActiveRecord::Base
2340
+ declare_schema { }
2341
+ belongs_to :ad_category, optional: #{nullable}
2342
+ end
2343
+ EOS
2344
+ expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional_flag[nullable])
2345
+ expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
2346
+ end
1173
2347
  end
1174
2348
  end
1175
2349
  end
1176
2350
  end
1177
- end
1178
2351
 
1179
- describe 'migration base class' do
1180
- it 'adapts to Rails 4' do
1181
- class Advert < active_record_base_class.constantize
1182
- fields do
1183
- title :string, limit: 100
2352
+ describe 'migration base class' do
2353
+ it 'adapts to Rails 4' do
2354
+ class Advert < active_record_base_class.constantize
2355
+ declare_schema do
2356
+ string :title, limit: 100
2357
+ end
1184
2358
  end
1185
- end
1186
2359
 
1187
- generate_migrations '-n', '-m'
2360
+ generate_migrations '-n', '-m'
1188
2361
 
1189
- migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1190
- expect(migrations.size).to eq(1), migrations.inspect
2362
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
2363
+ expect(migrations.size).to eq(1), migrations.inspect
1191
2364
 
1192
- migration_content = File.read(migrations.first)
1193
- first_line = migration_content.split("\n").first
1194
- base_class = first_line.split(' < ').last
1195
- expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
2365
+ migration_content = File.read(migrations.first)
2366
+ first_line = migration_content.split("\n").first
2367
+ base_class = first_line.split(' < ').last
2368
+ expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
2369
+ end
1196
2370
  end
1197
- end
1198
2371
 
1199
- context 'Does not generate migrations' do
1200
- it 'for aliased fields bigint -> integer limit 8' do
1201
- if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
1202
- class Advert < active_record_base_class.constantize
1203
- fields do
1204
- price :bigint
2372
+ context 'Does not generate migrations' do
2373
+ it 'for aliased fields bigint -> integer limit 8' do
2374
+ if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
2375
+ class Advert < active_record_base_class.constantize
2376
+ declare_schema do
2377
+ bigint :price
2378
+ end
1205
2379
  end
1206
- end
1207
2380
 
1208
- generate_migrations '-n', '-m'
2381
+ generate_migrations '-n', '-m'
1209
2382
 
1210
- migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
1211
- expect(migrations.size).to eq(1), migrations.inspect
2383
+ migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
2384
+ expect(migrations.size).to eq(1), migrations.inspect
1212
2385
 
1213
- if defined?(Mysql2) && Rails::VERSION::MAJOR < 5
1214
- ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
1215
- end
2386
+ if defined?(Mysql2) && Rails::VERSION::MAJOR < 5
2387
+ ActiveRecord::Base.connection.execute("ALTER TABLE adverts ADD PRIMARY KEY (id)")
2388
+ end
1216
2389
 
1217
- class Advert < active_record_base_class.constantize
1218
- fields do
1219
- price :integer, limit: 8
2390
+ class Advert < active_record_base_class.constantize
2391
+ declare_schema do
2392
+ integer :price, limit: 8
2393
+ end
1220
2394
  end
1221
- end
1222
2395
 
1223
- expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
2396
+ expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
2397
+ end
1224
2398
  end
1225
2399
  end
1226
2400
  end