declare_schema 0.9.0 → 0.10.0.pre.dc.1

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