declare_schema 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +14 -0
- data/.github/workflows/declare_schema_build.yml +44 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +19 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +66 -0
- data/gemfiles/rails_4.gemfile +1 -0
- data/gemfiles/rails_5.gemfile +1 -0
- data/gemfiles/rails_6.gemfile +1 -0
- data/lib/declare_schema.rb +1 -0
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +2 -1
- data/lib/declare_schema/model.rb +5 -1
- data/lib/declare_schema/model/field_spec.rb +90 -30
- data/lib/declare_schema/model/table_options_definition.rb +83 -0
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +97 -42
- data/spec/lib/declare_schema/field_spec_spec.rb +69 -0
- data/spec/lib/declare_schema/generator_spec.rb +22 -9
- data/spec/lib/declare_schema/migration_generator_spec.rb +25 -0
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +84 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +28 -0
- metadata +10 -7
- data/.dependabot/config.yml +0 -10
- data/.travis.yml +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0c13793a5b263a2f1134ee19168a42ceb57c472cf8fb656b8e86fcdd9500b79
|
|
4
|
+
data.tar.gz: ee92620b0a091538958957205ba940d97cd9f67bc606751cb52a8ae44370e541
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bc0e18dd1b3a998640cc2e983c86182e19b855d8ce42b3f7233b3471a7849d0dc28fa2653d110b42d0544c513267f8066386496b1469085bb507541c068b87a
|
|
7
|
+
data.tar.gz: 34b9e31862291b85becf7eeb908c4ef1da5b29178b1ab387b35c6678680299cd921fa65b49e0ba89604518a66515092d3e7d6c38678267301c9399b4572d372e
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: bundler
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: friday
|
|
8
|
+
time: "22:00"
|
|
9
|
+
timezone: PST8PDT
|
|
10
|
+
open-pull-requests-limit: 99
|
|
11
|
+
versioning-strategy: lockfile-only
|
|
12
|
+
commit-message:
|
|
13
|
+
prefix: No-Jira
|
|
14
|
+
include: scope
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
on: [push]
|
|
3
|
+
|
|
4
|
+
name: DeclareSchema Build
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
build:
|
|
8
|
+
name: DeclareSchema Build
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
ruby: [ 2.4.5, 2.5.8, 2.6.5, 2.7.1 ]
|
|
13
|
+
gemfile: [ gemfiles/rails_4.gemfile, gemfiles/rails_5.gemfile, gemfiles/rails_6.gemfile ]
|
|
14
|
+
exclude:
|
|
15
|
+
- { gemfile: gemfiles/rails_4.gemfile, ruby: 2.7.1 }
|
|
16
|
+
- { gemfile: gemfiles/rails_5.gemfile, ruby: 2.4.5 }
|
|
17
|
+
- { gemfile: gemfiles/rails_6.gemfile, ruby: 2.4.5 }
|
|
18
|
+
env:
|
|
19
|
+
BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout Branch
|
|
22
|
+
id: checkout_branch
|
|
23
|
+
uses: actions/checkout@v2
|
|
24
|
+
- name: Setup Ruby
|
|
25
|
+
id: setup_ruby
|
|
26
|
+
uses: ruby/setup-ruby@v1
|
|
27
|
+
with:
|
|
28
|
+
bundler: 1.17.3
|
|
29
|
+
ruby-version: ${{matrix.ruby}}
|
|
30
|
+
- name: Remove Bundler 2
|
|
31
|
+
id: remove_bundler_2
|
|
32
|
+
if: ${{ matrix.ruby >= '2.6.5' }}
|
|
33
|
+
run: |
|
|
34
|
+
rm -f /opt/hostedtoolcache/Ruby/2.*/x64/lib/ruby/gems/2.*/specifications/default/bundler-2.*.gemspec
|
|
35
|
+
gem install bundler:1.17.3 --force --default
|
|
36
|
+
gem install bundler -v 1.17.3
|
|
37
|
+
- name: Appraisals
|
|
38
|
+
id: appraisals
|
|
39
|
+
run: |
|
|
40
|
+
bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} --gemfile=${{ matrix.gemfile }}
|
|
41
|
+
git config --global user.email "dummy@example.com"
|
|
42
|
+
git config --global user.name "dummy"
|
|
43
|
+
bundle exec rake test:prepare_testapp[force]
|
|
44
|
+
bundle exec rake test:all < test_responses.txt
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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.6.0] - 2020-12-23
|
|
8
|
+
### Added
|
|
9
|
+
- Fields may now be declared with `:bigint` type which is identical to `:integer, limit 8`
|
|
10
|
+
- FieldSpec#initialize interface now includes `position` keyword argument and `**options` hash.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fixed cycle in which FieldSpec#initialize was calling `model.field_specs`
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Changed ci support from Travis to Github Workflow
|
|
17
|
+
|
|
18
|
+
## [0.5.0] - 2020-12-21
|
|
19
|
+
### Added
|
|
20
|
+
- Added support for configuring the character set and collation for MySQL databases
|
|
21
|
+
at the global, table, and field level
|
|
22
|
+
|
|
7
23
|
## [0.4.2] - 2020-12-05
|
|
8
24
|
### Fixed
|
|
9
25
|
- Generalize the fix below to sqlite || Rails 4.
|
|
@@ -15,7 +31,7 @@ Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0
|
|
|
15
31
|
## [0.4.0] - 2020-11-20
|
|
16
32
|
### Added
|
|
17
33
|
- Fields may be declared with `serialize: true` (any value with a valid `.to_yaml` stored as YAML),
|
|
18
|
-
or `serialize: <serializeable-class>`, where `<serializeable-class>`
|
|
34
|
+
or `serialize: <serializeable-class>`, where `<serializeable-class>`
|
|
19
35
|
may be `Array` (`Array` stored as YAML) or `Hash` (`Hash` stored as YAML) or `JSON` (any value with a valid `.to_json`, stored as JSON)
|
|
20
36
|
or any custom serializable class.
|
|
21
37
|
This invokes `ActiveSupport`'s `serialize` macro for that field, passing the serializable class, if given.
|
|
@@ -68,6 +84,8 @@ using the appropriate Rails configuration attributes.
|
|
|
68
84
|
### Added
|
|
69
85
|
- Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
|
|
70
86
|
|
|
87
|
+
[0.6.0]: https://github.com/Invoca/declare_schema/compare/v0.5.0...v0.6.0
|
|
88
|
+
[0.5.0]: https://github.com/Invoca/declare_schema/compare/v0.4.2...v0.5.0
|
|
71
89
|
[0.4.2]: https://github.com/Invoca/declare_schema/compare/v0.4.1...v0.4.2
|
|
72
90
|
[0.4.1]: https://github.com/Invoca/declare_schema/compare/v0.4.0...v0.4.1
|
|
73
91
|
[0.4.0]: https://github.com/Invoca/declare_schema/compare/v0.3.1...v0.4.0
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
declare_schema (0.
|
|
4
|
+
declare_schema (0.6.0)
|
|
5
5
|
rails (>= 4.2)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -85,6 +85,7 @@ GEM
|
|
|
85
85
|
mini_portile2 (2.4.0)
|
|
86
86
|
minitest (5.14.2)
|
|
87
87
|
msgpack (1.3.3)
|
|
88
|
+
mysql2 (0.5.3)
|
|
88
89
|
nio4r (2.5.4)
|
|
89
90
|
nokogiri (1.10.10)
|
|
90
91
|
mini_portile2 (~> 2.4.0)
|
|
@@ -187,6 +188,7 @@ DEPENDENCIES
|
|
|
187
188
|
climate_control (~> 0.2)
|
|
188
189
|
declare_schema!
|
|
189
190
|
listen
|
|
191
|
+
mysql2
|
|
190
192
|
pry
|
|
191
193
|
pry-byebug
|
|
192
194
|
rails (~> 5.2, >= 5.2.4.3)
|
data/README.md
CHANGED
|
@@ -70,6 +70,72 @@ DeclareSchema::Migration::Migrator.before_generating_migration do
|
|
|
70
70
|
end
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
## Declaring Character Set and Collation
|
|
74
|
+
_Note: This feature currently only works for MySQL database configurations._
|
|
75
|
+
|
|
76
|
+
MySQL originally supported UTF-8 in the range of 1-3 bytes (`mb3` or "multi-byte 3")
|
|
77
|
+
which covered the full set of Unicode code points at the time: U+0000 - U+FFFF.
|
|
78
|
+
But later, Unicode was extended beyond U+FFFF to make room for emojis, and with that
|
|
79
|
+
UTF-8 require 1-4 bytes (`mb4` or "multi-byte 4"). With this addition, there has
|
|
80
|
+
come a need to dynamically define the character set and collation for individual
|
|
81
|
+
tables and columns in the database. With `declare_schema` this can be configured
|
|
82
|
+
at three separate levels
|
|
83
|
+
|
|
84
|
+
### Global Configuration
|
|
85
|
+
The character set and collation for all tables and fields can be set at the global level
|
|
86
|
+
using the `Generators::DeclareSchema::Migrator.default_charset=` and
|
|
87
|
+
`Generators::DeclareSchema::Migrator.default_collation=` configuration methods.
|
|
88
|
+
|
|
89
|
+
For example, adding the following to your `config/initializers` directory will
|
|
90
|
+
turn all tables into `utf8mb4` supporting tables:
|
|
91
|
+
|
|
92
|
+
**declare_schema.rb**
|
|
93
|
+
```ruby
|
|
94
|
+
# frozen_string_literal: true
|
|
95
|
+
|
|
96
|
+
Generators::DeclareSchema::Migration::Migrator.default_charset = "utf8mb4"
|
|
97
|
+
Generators::DeclareSchema::Migration::Migrator.default_collation = "utf8mb4_general"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Table Configuration
|
|
101
|
+
In order to configure a table's default character set and collation, the `charset` and
|
|
102
|
+
`collation` arguments can be added to the `fields` block.
|
|
103
|
+
|
|
104
|
+
For example, if you have a comments model that needs `utf8mb4` support, it would look
|
|
105
|
+
like the following:
|
|
106
|
+
|
|
107
|
+
**app/models/comment.rb**
|
|
108
|
+
```ruby
|
|
109
|
+
# frozen_string_literal: true
|
|
110
|
+
|
|
111
|
+
class Comment < ActiveRecord::Base
|
|
112
|
+
fields charset: "utf8mb4", collation: "utf8mb4_general" do
|
|
113
|
+
subject :string, limit: 255
|
|
114
|
+
content :text, limit: 0xffff_ffff
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Field Configuration
|
|
120
|
+
If you're looking to only change the character set and collation for a single field
|
|
121
|
+
in the table, simply set the `charset` and `collation` configuration options on the
|
|
122
|
+
field definition itself.
|
|
123
|
+
|
|
124
|
+
For example, if you only want to support `utf8mb4` for the content of a comment, it would
|
|
125
|
+
look like the following:
|
|
126
|
+
|
|
127
|
+
**app/models/comment.rb**
|
|
128
|
+
```ruby
|
|
129
|
+
# frozen_string_literal: true
|
|
130
|
+
|
|
131
|
+
class Comment < ActiveRecord::Base
|
|
132
|
+
fields do
|
|
133
|
+
subject :string, limit: 255
|
|
134
|
+
context :text, limit: 0xffff_ffff, charset: "utf8mb4", collation: "utf8mb4_general"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
73
139
|
## Installing
|
|
74
140
|
|
|
75
141
|
Install the `DeclareSchema` gem directly:
|
data/gemfiles/rails_4.gemfile
CHANGED
data/gemfiles/rails_5.gemfile
CHANGED
data/gemfiles/rails_6.gemfile
CHANGED
data/lib/declare_schema.rb
CHANGED
|
@@ -41,5 +41,6 @@ require 'declare_schema/model'
|
|
|
41
41
|
require 'declare_schema/model/field_spec'
|
|
42
42
|
require 'declare_schema/model/index_definition'
|
|
43
43
|
require 'declare_schema/model/foreign_key_definition'
|
|
44
|
+
require 'declare_schema/model/table_options_definition'
|
|
44
45
|
|
|
45
46
|
require 'declare_schema/railtie' if defined?(Rails)
|
|
@@ -6,12 +6,13 @@ require 'declare_schema/field_declaration_dsl'
|
|
|
6
6
|
|
|
7
7
|
module DeclareSchema
|
|
8
8
|
module FieldsDsl
|
|
9
|
-
def fields(&block)
|
|
9
|
+
def fields(table_options = {}, &block)
|
|
10
10
|
# Any model that calls 'fields' gets DeclareSchema::Model behavior
|
|
11
11
|
DeclareSchema::Model.mix_in(self)
|
|
12
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
|
+
@table_options = table_options
|
|
15
16
|
|
|
16
17
|
if block
|
|
17
18
|
dsl = DeclareSchema::FieldDeclarationDsl.new(self, null: false)
|
data/lib/declare_schema/model.rb
CHANGED
|
@@ -30,6 +30,10 @@ module DeclareSchema
|
|
|
30
30
|
inheriting_cattr_reader ignore_indexes: []
|
|
31
31
|
inheriting_cattr_reader constraint_specs: []
|
|
32
32
|
|
|
33
|
+
# table_options holds optional configuration for the create_table statement
|
|
34
|
+
# supported options include :charset and :collation
|
|
35
|
+
inheriting_cattr_reader table_options: HashWithIndifferentAccess.new
|
|
36
|
+
|
|
33
37
|
# eval avoids the ruby 1.9.2 "super from singleton method ..." error
|
|
34
38
|
|
|
35
39
|
eval %(
|
|
@@ -84,7 +88,7 @@ module DeclareSchema
|
|
|
84
88
|
add_formatting_for_field(name, type)
|
|
85
89
|
add_validations_for_field(name, type, args, options)
|
|
86
90
|
add_index_for_field(name, args, options)
|
|
87
|
-
field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, options)
|
|
91
|
+
field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
|
|
88
92
|
attr_order << name unless attr_order.include?(name)
|
|
89
93
|
end
|
|
90
94
|
|
|
@@ -33,7 +33,8 @@ module DeclareSchema
|
|
|
33
33
|
|
|
34
34
|
attr_reader :model, :name, :type, :position, :options
|
|
35
35
|
|
|
36
|
-
def initialize(model, name, type,
|
|
36
|
+
def initialize(model, name, type, position: 0, **options)
|
|
37
|
+
# TODO: TECH-5116
|
|
37
38
|
# Invoca change - searching for the primary key was causing an additional database read on every model load. Assume
|
|
38
39
|
# "id" which works for invoca.
|
|
39
40
|
# raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
|
|
@@ -43,9 +44,8 @@ module DeclareSchema
|
|
|
43
44
|
@name = name.to_sym
|
|
44
45
|
type.is_a?(Symbol) or raise ArgumentError, "type must be a Symbol; got #{type.inspect}"
|
|
45
46
|
@type = type
|
|
46
|
-
|
|
47
|
+
@position = position
|
|
47
48
|
@options = options
|
|
48
|
-
|
|
49
49
|
case type
|
|
50
50
|
when :text
|
|
51
51
|
@options[:default] and raise "default may not be given for :text field #{model}##{@name}"
|
|
@@ -54,8 +54,15 @@ module DeclareSchema
|
|
|
54
54
|
end
|
|
55
55
|
when :string
|
|
56
56
|
@options[:limit] or raise "limit must be given for :string field #{model}##{@name}: #{@options.inspect}; do you want `limit: 255`?"
|
|
57
|
+
when :bigint
|
|
58
|
+
@type = :integer
|
|
59
|
+
@options = options.merge(limit: 8)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
unless type.in?([:text, :string])
|
|
63
|
+
@options[:collation] and raise "collation may only given for :string and :text fields"
|
|
64
|
+
@options[:charset] and raise "charset may only given for :string and :text fields"
|
|
57
65
|
end
|
|
58
|
-
@position = position_option || model.field_specs.length
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
TYPE_SYNONYMS = { timestamp: :datetime }.freeze
|
|
@@ -102,6 +109,18 @@ module DeclareSchema
|
|
|
102
109
|
@options[:default]
|
|
103
110
|
end
|
|
104
111
|
|
|
112
|
+
def collation
|
|
113
|
+
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
|
114
|
+
(@options[:collation] || model.table_options[:collation] || Generators::DeclareSchema::Migration::Migrator.default_collation).to_s
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def charset
|
|
119
|
+
if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
|
120
|
+
(@options[:charset] || model.table_options[:charset] || Generators::DeclareSchema::Migration::Migrator.default_charset).to_s
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
105
124
|
def same_type?(col_spec)
|
|
106
125
|
type = sql_type
|
|
107
126
|
normalized_type = TYPE_SYNONYMS[type] || type
|
|
@@ -109,36 +128,77 @@ module DeclareSchema
|
|
|
109
128
|
normalized_type == normalized_col_spec_type
|
|
110
129
|
end
|
|
111
130
|
|
|
112
|
-
def different_to?(col_spec)
|
|
113
|
-
!
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
def different_to?(table_name, col_spec)
|
|
132
|
+
!same_as(table_name, col_spec)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def same_as(table_name, col_spec)
|
|
136
|
+
same_type?(col_spec) &&
|
|
137
|
+
same_attributes?(col_spec) &&
|
|
138
|
+
(!type.in?([:text, :string]) || same_charset_and_collation?(table_name, col_spec))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def same_attributes?(col_spec)
|
|
144
|
+
native_type = native_types[type]
|
|
145
|
+
check_attributes = [:null, :default]
|
|
146
|
+
check_attributes += [:precision, :scale] if sql_type == :decimal && !col_spec.is_a?(SQLITE_COLUMN_CLASS) # remove when rails fixes https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2872
|
|
147
|
+
check_attributes -= [:default] if sql_type == :text && col_spec.class.name =~ /mysql/i
|
|
148
|
+
check_attributes << :limit if sql_type.in?([:string, :binary, :varbinary, :integer, :enum]) ||
|
|
149
|
+
(sql_type == :text && self.class.mysql_text_limits?)
|
|
150
|
+
check_attributes.all? do |k|
|
|
151
|
+
if k == :default
|
|
152
|
+
case Rails::VERSION::MAJOR
|
|
153
|
+
when 4
|
|
154
|
+
col_spec.type_cast_from_database(col_spec.default) == col_spec.type_cast_from_database(default)
|
|
155
|
+
else
|
|
156
|
+
cast_type = ActiveRecord::Base.connection.lookup_cast_type_from_column(col_spec) or raise "cast_type not found for #{col_spec.inspect}"
|
|
157
|
+
cast_type.deserialize(col_spec.default) == cast_type.deserialize(default)
|
|
137
158
|
end
|
|
159
|
+
else
|
|
160
|
+
col_value = col_spec.send(k)
|
|
161
|
+
if col_value.nil? && native_type
|
|
162
|
+
col_value = native_type[k]
|
|
163
|
+
end
|
|
164
|
+
col_value == send(k)
|
|
138
165
|
end
|
|
166
|
+
end
|
|
139
167
|
end
|
|
140
168
|
|
|
141
|
-
|
|
169
|
+
def same_charset_and_collation?(table_name, col_spec)
|
|
170
|
+
current_collation_and_charset = collation_and_charset_for_column(table_name, col_spec)
|
|
171
|
+
|
|
172
|
+
collation == current_collation_and_charset[:collation] &&
|
|
173
|
+
charset == current_collation_and_charset[:charset]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def collation_and_charset_for_column(table_name, col_spec)
|
|
177
|
+
column_name = col_spec.name
|
|
178
|
+
connection = ActiveRecord::Base.connection
|
|
179
|
+
|
|
180
|
+
if connection.class.name.match?(/mysql/i)
|
|
181
|
+
database_name = connection.current_database
|
|
182
|
+
|
|
183
|
+
defaults = connection.select_one(<<~EOS)
|
|
184
|
+
SELECT C.character_set_name, C.collation_name
|
|
185
|
+
FROM information_schema.`COLUMNS` C
|
|
186
|
+
WHERE C.table_schema = '#{connection.quote_string(database_name)}' AND
|
|
187
|
+
C.table_name = '#{connection.quote_string(table_name)}' AND
|
|
188
|
+
C.column_name = '#{connection.quote_string(column_name)}';
|
|
189
|
+
EOS
|
|
190
|
+
|
|
191
|
+
defaults["character_set_name"] or raise "character_set_name missing from #{defaults.inspect}"
|
|
192
|
+
defaults["collation_name"] or raise "collation_name missing from #{defaults.inspect}"
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
charset: defaults["character_set_name"],
|
|
196
|
+
collation: defaults["collation_name"]
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
{}
|
|
200
|
+
end
|
|
201
|
+
end
|
|
142
202
|
|
|
143
203
|
def native_type?(type)
|
|
144
204
|
type.to_sym != :primary_key && native_types.has_key?(type)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeclareSchema
|
|
4
|
+
module Model
|
|
5
|
+
class TableOptionsDefinition
|
|
6
|
+
include Comparable
|
|
7
|
+
|
|
8
|
+
TABLE_OPTIONS_TO_SQL_MAPPINGS = {
|
|
9
|
+
charset: 'CHARACTER SET',
|
|
10
|
+
collation: 'COLLATE'
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def for_model(model, old_table_name = nil)
|
|
15
|
+
table_name = old_table_name || model.table_name
|
|
16
|
+
table_options = if model.connection.class.name.match?(/mysql/i)
|
|
17
|
+
mysql_table_options(model.connection, table_name)
|
|
18
|
+
else
|
|
19
|
+
{}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
new(table_name, table_options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def mysql_table_options(connection, table_name)
|
|
28
|
+
database = connection.current_database
|
|
29
|
+
defaults = connection.select_one(<<~EOS)
|
|
30
|
+
SELECT CCSA.character_set_name, CCSA.collation_name
|
|
31
|
+
FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA
|
|
32
|
+
WHERE CCSA.collation_name = T.table_collation AND
|
|
33
|
+
T.table_schema = '#{connection.quote_string(database)}' AND
|
|
34
|
+
T.table_name = '#{connection.quote_string(table_name)}';
|
|
35
|
+
EOS
|
|
36
|
+
|
|
37
|
+
defaults["character_set_name"] or raise "character_set_name missing from #{defaults.inspect}"
|
|
38
|
+
defaults["collation_name"] or raise "collation_name missing from #{defaults.inspect}"
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
charset: defaults["character_set_name"],
|
|
42
|
+
collation: defaults["collation_name"]
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :table_name, :table_options
|
|
48
|
+
|
|
49
|
+
def initialize(table_name, table_options = {})
|
|
50
|
+
@table_name = table_name
|
|
51
|
+
@table_options = table_options
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_key
|
|
55
|
+
@key ||= [table_name, table_options].map(&:to_s)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def settings
|
|
59
|
+
@settings ||= table_options.map { |name, value| "#{TABLE_OPTIONS_TO_SQL_MAPPINGS[name]} #{value}" if value }.compact.join(" ")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def hash
|
|
63
|
+
to_key.hash
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def <=>(rhs)
|
|
67
|
+
to_key <=> rhs.to_key
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def equivalent?(rhs)
|
|
71
|
+
settings == rhs.settings
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
alias eql? ==
|
|
75
|
+
alias to_s settings
|
|
76
|
+
|
|
77
|
+
def alter_table_statement
|
|
78
|
+
statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} #{to_s};"
|
|
79
|
+
"execute #{statement.inspect}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -23,6 +23,10 @@ module Generators
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def table_options
|
|
27
|
+
{}
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def table_name
|
|
27
31
|
join_table
|
|
28
32
|
end
|
|
@@ -33,11 +37,9 @@ module Generators
|
|
|
33
37
|
|
|
34
38
|
def field_specs
|
|
35
39
|
i = 0
|
|
36
|
-
foreign_keys.
|
|
37
|
-
|
|
38
|
-
h[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :integer, position: i, null: false)
|
|
40
|
+
foreign_keys.each_with_object({}) do |v, result|
|
|
41
|
+
result[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :integer, position: i, null: false)
|
|
39
42
|
i += 1
|
|
40
|
-
h
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -69,13 +71,19 @@ module Generators
|
|
|
69
71
|
class Migrator
|
|
70
72
|
class Error < RuntimeError; end
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
DEFAULT_CHARSET = :utf8mb4
|
|
75
|
+
DEFAULT_COLLATION = :utf8mb4_general
|
|
76
|
+
|
|
77
|
+
@ignore_models = []
|
|
78
|
+
@ignore_tables = []
|
|
74
79
|
@before_generating_migration_callback = nil
|
|
75
|
-
@active_record_class
|
|
80
|
+
@active_record_class = ActiveRecord::Base
|
|
81
|
+
@default_charset = DEFAULT_CHARSET
|
|
82
|
+
@default_collation = DEFAULT_COLLATION
|
|
76
83
|
|
|
77
84
|
class << self
|
|
78
|
-
attr_accessor :ignore_models, :ignore_tables, :disable_indexing, :disable_constraints,
|
|
85
|
+
attr_accessor :ignore_models, :ignore_tables, :disable_indexing, :disable_constraints,
|
|
86
|
+
:active_record_class, :default_charset, :default_collation
|
|
79
87
|
attr_reader :before_generating_migration_callback
|
|
80
88
|
|
|
81
89
|
def active_record_class
|
|
@@ -292,52 +300,76 @@ module Generators
|
|
|
292
300
|
"drop_table :#{t}"
|
|
293
301
|
end * "\n"
|
|
294
302
|
|
|
295
|
-
changes
|
|
296
|
-
undo_changes
|
|
297
|
-
index_changes
|
|
298
|
-
undo_index_changes
|
|
299
|
-
fk_changes
|
|
300
|
-
undo_fk_changes
|
|
303
|
+
changes = []
|
|
304
|
+
undo_changes = []
|
|
305
|
+
index_changes = []
|
|
306
|
+
undo_index_changes = []
|
|
307
|
+
fk_changes = []
|
|
308
|
+
undo_fk_changes = []
|
|
309
|
+
table_options_changes = []
|
|
310
|
+
undo_table_options_changes = []
|
|
311
|
+
|
|
301
312
|
to_change.each do |t|
|
|
302
313
|
model = models_by_table_name[t]
|
|
303
314
|
table = to_rename.key(t) || model.table_name
|
|
304
315
|
if table.in?(db_tables)
|
|
305
|
-
change, undo, index_change, undo_index, fk_change, undo_fk = change_table(model, table)
|
|
316
|
+
change, undo, index_change, undo_index, fk_change, undo_fk, table_options_change, undo_table_options_change = change_table(model, table)
|
|
306
317
|
changes << change
|
|
307
318
|
undo_changes << undo
|
|
308
319
|
index_changes << index_change
|
|
309
320
|
undo_index_changes << undo_index
|
|
310
321
|
fk_changes << fk_change
|
|
311
322
|
undo_fk_changes << undo_fk
|
|
323
|
+
table_options_changes << table_options_change
|
|
324
|
+
undo_table_options_changes << undo_table_options_change
|
|
312
325
|
end
|
|
313
326
|
end
|
|
314
327
|
|
|
315
|
-
up = [renames, drops, creates, changes, index_changes, fk_changes].flatten.reject(&:blank?) * "\n\n"
|
|
316
|
-
down = [undo_changes, undo_renames, undo_drops, undo_creates, undo_index_changes, undo_fk_changes].flatten.reject(&:blank?) * "\n\n"
|
|
328
|
+
up = [renames, drops, creates, changes, index_changes, fk_changes, table_options_changes].flatten.reject(&:blank?) * "\n\n"
|
|
329
|
+
down = [undo_changes, undo_renames, undo_drops, undo_creates, undo_index_changes, undo_fk_changes, undo_table_options_changes].flatten.reject(&:blank?) * "\n\n"
|
|
317
330
|
|
|
318
331
|
[up, down]
|
|
319
332
|
end
|
|
320
333
|
|
|
321
334
|
def create_table(model)
|
|
322
|
-
longest_field_name
|
|
323
|
-
disable_auto_increment
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
longest_field_name = model.field_specs.values.map { |f| f.sql_type.to_s.length }.max
|
|
336
|
+
disable_auto_increment = model.respond_to?(:disable_auto_increment) && model.disable_auto_increment
|
|
337
|
+
table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
|
338
|
+
field_definitions = [
|
|
339
|
+
disable_auto_increment ? "t.integer :id, limit: 8, auto_increment: false, primary_key: true" : nil,
|
|
340
|
+
*(model.field_specs.values.sort_by(&:position).map { |f| create_field(f, longest_field_name) })
|
|
341
|
+
].compact
|
|
342
|
+
|
|
343
|
+
<<~EOS.strip
|
|
344
|
+
create_table :#{model.table_name}, #{create_table_options(model, disable_auto_increment)} do |t|
|
|
345
|
+
#{field_definitions.join("\n")}
|
|
331
346
|
end
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
|
|
348
|
+
#{table_options_definition.alter_table_statement unless ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)}
|
|
349
|
+
#{create_indexes(model).join("\n") unless Migrator.disable_indexing}
|
|
350
|
+
#{create_constraints(model).join("\n") unless Migrator.disable_indexing}
|
|
351
|
+
EOS
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def create_table_options(model, disable_auto_increment)
|
|
355
|
+
if model.primary_key.blank? || disable_auto_increment
|
|
356
|
+
"id: false"
|
|
357
|
+
elsif model.primary_key == "id"
|
|
358
|
+
"id: :bigint"
|
|
359
|
+
else
|
|
360
|
+
"primary_key: :#{model.primary_key}"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def table_options_for_model(model)
|
|
365
|
+
if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
|
366
|
+
{}
|
|
367
|
+
else
|
|
368
|
+
{
|
|
369
|
+
charset: model.table_options[:charset] || Migrator.default_charset,
|
|
370
|
+
collation: model.table_options[:collation] || Migrator.default_collation
|
|
371
|
+
}
|
|
372
|
+
end
|
|
341
373
|
end
|
|
342
374
|
|
|
343
375
|
def create_indexes(model)
|
|
@@ -351,7 +383,7 @@ module Generators
|
|
|
351
383
|
def create_field(field_spec, field_name_width)
|
|
352
384
|
options = fk_field_options(field_spec.model, field_spec.name).merge(field_spec.sql_options)
|
|
353
385
|
args = [field_spec.name.inspect] + format_options(options, field_spec.sql_type)
|
|
354
|
-
format("
|
|
386
|
+
format("t.%-*s %s", field_name_width, field_spec.sql_type, args.join(', '))
|
|
355
387
|
end
|
|
356
388
|
|
|
357
389
|
def change_table(model, current_table_name)
|
|
@@ -413,15 +445,17 @@ module Generators
|
|
|
413
445
|
col_name = old_names[c] || c
|
|
414
446
|
col = db_columns[col_name]
|
|
415
447
|
spec = model.field_specs[c]
|
|
416
|
-
if spec.different_to?(col) # TODO: TECH-4814 DRY this up to a diff function that returns the differences. It's different if it has differences. -Colin
|
|
448
|
+
if spec.different_to?(current_table_name, col) # TODO: TECH-4814 DRY this up to a diff function that returns the differences. It's different if it has differences. -Colin
|
|
417
449
|
change_spec = fk_field_options(model, c)
|
|
418
450
|
change_spec[:limit] ||= spec.limit if (spec.sql_type != :text ||
|
|
419
451
|
::DeclareSchema::Model::FieldSpec.mysql_text_limits?) &&
|
|
420
452
|
(spec.limit || col.limit)
|
|
421
|
-
change_spec[:precision]
|
|
422
|
-
change_spec[:scale]
|
|
423
|
-
change_spec[:null]
|
|
424
|
-
change_spec[:default]
|
|
453
|
+
change_spec[:precision] = spec.precision unless spec.precision.nil?
|
|
454
|
+
change_spec[:scale] = spec.scale unless spec.scale.nil?
|
|
455
|
+
change_spec[:null] = spec.null unless spec.null && col.null
|
|
456
|
+
change_spec[:default] = spec.default unless spec.default.nil? && col.default.nil?
|
|
457
|
+
change_spec[:collation] = spec.collation unless spec.collation.nil?
|
|
458
|
+
change_spec[:charset] = spec.charset unless spec.charset.nil?
|
|
425
459
|
|
|
426
460
|
changes << "change_column :#{new_table_name}, :#{c}, " +
|
|
427
461
|
([":#{spec.sql_type}"] + format_options(change_spec, spec.sql_type, changing: true)).join(", ")
|
|
@@ -436,13 +470,20 @@ module Generators
|
|
|
436
470
|
else
|
|
437
471
|
change_foreign_key_constraints(model, current_table_name)
|
|
438
472
|
end
|
|
473
|
+
table_options_changes, undo_table_options_changes = if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
|
474
|
+
change_table_options(model, current_table_name)
|
|
475
|
+
else
|
|
476
|
+
[[], []]
|
|
477
|
+
end
|
|
439
478
|
|
|
440
479
|
[(renames + adds + removes + changes) * "\n",
|
|
441
480
|
(undo_renames + undo_adds + undo_removes + undo_changes) * "\n",
|
|
442
481
|
index_changes * "\n",
|
|
443
482
|
undo_index_changes * "\n",
|
|
444
483
|
fk_changes * "\n",
|
|
445
|
-
undo_fk_changes * "\n"
|
|
484
|
+
undo_fk_changes * "\n",
|
|
485
|
+
table_options_changes * "\n",
|
|
486
|
+
undo_table_options_changes * "\n"]
|
|
446
487
|
end
|
|
447
488
|
|
|
448
489
|
def change_indexes(model, old_table_name)
|
|
@@ -552,6 +593,20 @@ module Generators
|
|
|
552
593
|
end
|
|
553
594
|
end
|
|
554
595
|
|
|
596
|
+
def change_table_options(model, current_table_name)
|
|
597
|
+
old_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.for_model(model, current_table_name)
|
|
598
|
+
new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
|
599
|
+
|
|
600
|
+
if old_options_definition.equivalent?(new_options_definition)
|
|
601
|
+
[[], []]
|
|
602
|
+
else
|
|
603
|
+
[
|
|
604
|
+
[new_options_definition.alter_table_statement],
|
|
605
|
+
[old_options_definition.alter_table_statement]
|
|
606
|
+
]
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
555
610
|
def revert_table(table)
|
|
556
611
|
res = StringIO.new
|
|
557
612
|
schema_dumper_klass = case Rails::VERSION::MAJOR
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DeclareSchema Model FieldSpec' do
|
|
4
|
+
before do
|
|
5
|
+
load File.expand_path('prepare_testapp.rb', __dir__)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
context 'There are no model columns to change' do
|
|
9
|
+
it '#different_to should return false for int8 == int8' do
|
|
10
|
+
subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :integer, limit: 8, null: false, position: 0)
|
|
11
|
+
|
|
12
|
+
case Rails::VERSION::MAJOR
|
|
13
|
+
when 4
|
|
14
|
+
cast_type = ActiveRecord::Type::Integer.new(limit: 8)
|
|
15
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
|
|
16
|
+
else
|
|
17
|
+
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "integer(8)", type: :integer, limit: 8)
|
|
18
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
expect(subject.different_to?(subject.name, col)).to eq(false)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it '#different_to should return false for bigint == bigint' do
|
|
25
|
+
subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :bigint, null: false, position: 0)
|
|
26
|
+
|
|
27
|
+
case Rails::VERSION::MAJOR
|
|
28
|
+
when 4
|
|
29
|
+
cast_type = ActiveRecord::Type::BigInteger.new(limit: 8)
|
|
30
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "bigint(20)", false)
|
|
31
|
+
else
|
|
32
|
+
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "bigint(20)", type: :integer, limit: 8)
|
|
33
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
expect(subject.different_to?(subject.name, col)).to eq(false)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it '#different_to should return false for int8 == bigint' do
|
|
40
|
+
subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :integer, limit: 8, null: false, position: 0)
|
|
41
|
+
|
|
42
|
+
case Rails::VERSION::MAJOR
|
|
43
|
+
when 4
|
|
44
|
+
cast_type = ActiveRecord::Type::BigInteger.new(limit: 8)
|
|
45
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "bigint(20)", false)
|
|
46
|
+
else
|
|
47
|
+
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "bigint(20)", type: :integer, limit: 8)
|
|
48
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
expect(subject.different_to?(subject.name, col)).to eq(false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it '#different_to should return false for bigint == int8' do
|
|
55
|
+
subject = DeclareSchema::Model::FieldSpec.new(Object, :price, :bigint, null: false, position: 0)
|
|
56
|
+
|
|
57
|
+
case Rails::VERSION::MAJOR
|
|
58
|
+
when 4
|
|
59
|
+
cast_type = ActiveRecord::Type::Integer.new(limit: 8)
|
|
60
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, cast_type, "integer(8)", false)
|
|
61
|
+
else
|
|
62
|
+
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(sql_type: "integer(8)", type: :integer, limit: 8)
|
|
63
|
+
col = ActiveRecord::ConnectionAdapters::Column.new("price", nil, sql_type_metadata, false, "adverts")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
expect(subject.different_to?(subject.name, col)).to eq(false)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -27,15 +27,28 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
27
27
|
end
|
|
28
28
|
EOS
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
case Rails::VERSION::MAJOR
|
|
31
|
+
when 4, 5
|
|
32
|
+
expect_test_definition_to_eq('alpha/beta', <<~EOS)
|
|
33
|
+
require 'test_helper'
|
|
34
|
+
|
|
35
|
+
class Alpha::BetaTest < ActiveSupport::TestCase
|
|
36
|
+
# test "the truth" do
|
|
37
|
+
# assert true
|
|
38
|
+
# end
|
|
39
|
+
end
|
|
40
|
+
EOS
|
|
41
|
+
else
|
|
42
|
+
expect_test_definition_to_eq('alpha/beta', <<~EOS)
|
|
43
|
+
require "test_helper"
|
|
44
|
+
|
|
45
|
+
class Alpha::BetaTest < ActiveSupport::TestCase
|
|
46
|
+
# test "the truth" do
|
|
47
|
+
# assert true
|
|
48
|
+
# end
|
|
49
|
+
end
|
|
50
|
+
EOS
|
|
51
|
+
end
|
|
39
52
|
|
|
40
53
|
case Rails::VERSION::MAJOR
|
|
41
54
|
when 4
|
|
@@ -1093,4 +1093,29 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
|
1093
1093
|
expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
|
|
1094
1094
|
end
|
|
1095
1095
|
end
|
|
1096
|
+
|
|
1097
|
+
context 'Does not generate migrations' do
|
|
1098
|
+
it 'for aliased fields bigint -> integer limit 8' do
|
|
1099
|
+
if Rails::VERSION::MAJOR >= 5 || !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
|
1100
|
+
class Advert < active_record_base_class.constantize
|
|
1101
|
+
fields do
|
|
1102
|
+
price :bigint
|
|
1103
|
+
end
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
generate_migrations '-n', '-m'
|
|
1107
|
+
|
|
1108
|
+
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
|
1109
|
+
expect(migrations.size).to eq(1), migrations.inspect
|
|
1110
|
+
|
|
1111
|
+
class Advert < active_record_base_class.constantize
|
|
1112
|
+
fields do
|
|
1113
|
+
price :integer, limit: 8
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
expect { generate_migrations '-n', '-g' }.to output("Database and models match -- nothing to change\n").to_stdout
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1096
1121
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
|
4
|
+
require_relative '../../../../lib/declare_schema/model/table_options_definition'
|
|
5
|
+
|
|
6
|
+
RSpec.describe DeclareSchema::Model::TableOptionsDefinition do
|
|
7
|
+
before do
|
|
8
|
+
load File.expand_path('../prepare_testapp.rb', __dir__)
|
|
9
|
+
|
|
10
|
+
class TableOptionsDefinitionTestModel < ActiveRecord::Base
|
|
11
|
+
fields do
|
|
12
|
+
name :string, limit: 127, index: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:model_class) { TableOptionsDefinitionTestModel }
|
|
18
|
+
|
|
19
|
+
context 'instance methods' do
|
|
20
|
+
let(:table_options) { { charset: "utf8", collation: "utf8_general"} }
|
|
21
|
+
let(:model) { described_class.new('table_options_definition_test_models', table_options) }
|
|
22
|
+
|
|
23
|
+
describe '#to_key' do
|
|
24
|
+
subject { model.to_key }
|
|
25
|
+
it { should eq(["table_options_definition_test_models", "{:charset=>\"utf8\", :collation=>\"utf8_general\"}"]) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#settings' do
|
|
29
|
+
subject { model.settings }
|
|
30
|
+
it { should eq("CHARACTER SET utf8 COLLATE utf8_general") }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#hash' do
|
|
34
|
+
subject { model.hash }
|
|
35
|
+
it { should eq(["table_options_definition_test_models", "{:charset=>\"utf8\", :collation=>\"utf8_general\"}"].hash) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#to_s' do
|
|
39
|
+
subject { model.to_s }
|
|
40
|
+
it { should eq("CHARACTER SET utf8 COLLATE utf8_general") }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#alter_table_statement' do
|
|
44
|
+
subject { model.alter_table_statement }
|
|
45
|
+
it { should eq('execute "ALTER TABLE \"table_options_definition_test_models\" CHARACTER SET utf8 COLLATE utf8_general;"') }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
context 'class << self' do
|
|
51
|
+
describe '#for_model' do
|
|
52
|
+
context 'when using a SQLite connection' do
|
|
53
|
+
subject { described_class.for_model(model_class) }
|
|
54
|
+
it { should eq(described_class.new(model_class.table_name, {})) }
|
|
55
|
+
end
|
|
56
|
+
# TODO: Convert these tests to run against a MySQL database so that we can
|
|
57
|
+
# perform them without mocking out so much
|
|
58
|
+
context 'when using a MySQL connection' do
|
|
59
|
+
before do
|
|
60
|
+
double(ActiveRecord::ConnectionAdapters::Mysql2Adapter).tap do |stub_connection|
|
|
61
|
+
expect(stub_connection).to receive(:class).and_return(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
|
62
|
+
expect(stub_connection).to receive(:current_database).and_return('test_database')
|
|
63
|
+
expect(stub_connection).to receive(:quote_string).with('test_database').and_return('test_database')
|
|
64
|
+
expect(stub_connection).to receive(:quote_string).with(model_class.table_name).and_return(model_class.table_name)
|
|
65
|
+
expect(stub_connection).to(
|
|
66
|
+
receive(:select_one).with(<<~EOS)
|
|
67
|
+
SELECT CCSA.character_set_name, CCSA.collation_name
|
|
68
|
+
FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA
|
|
69
|
+
WHERE CCSA.collation_name = T.table_collation AND
|
|
70
|
+
T.table_schema = 'test_database' AND
|
|
71
|
+
T.table_name = '#{model_class.table_name}';
|
|
72
|
+
EOS
|
|
73
|
+
.and_return({ "character_set_name" => "utf8", "collation_name" => "utf8_general" })
|
|
74
|
+
)
|
|
75
|
+
allow(model_class).to receive(:connection).and_return(stub_connection)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
subject { described_class.for_model(model_class) }
|
|
80
|
+
it { should eq(described_class.new(model_class.table_name, { charset: "utf8", collation: "utf8_general" })) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -43,6 +43,34 @@ module Generators
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
describe '#default_charset' do
|
|
47
|
+
subject { described_class.default_charset }
|
|
48
|
+
|
|
49
|
+
context 'when not explicitly set' do
|
|
50
|
+
it { should eq(:utf8mb4) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'when explicitly set' do
|
|
54
|
+
before { described_class.default_charset = :utf8 }
|
|
55
|
+
after { described_class.default_charset = described_class::DEFAULT_CHARSET }
|
|
56
|
+
it { should eq(:utf8) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '#default_collation' do
|
|
61
|
+
subject { described_class.default_collation }
|
|
62
|
+
|
|
63
|
+
context 'when not explicitly set' do
|
|
64
|
+
it { should eq(:utf8mb4_general) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'when explicitly set' do
|
|
68
|
+
before { described_class.default_collation = :utf8mb4_general_ci }
|
|
69
|
+
after { described_class.default_collation = described_class::DEFAULT_COLLATION }
|
|
70
|
+
it { should eq(:utf8mb4_general_ci) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
46
74
|
describe 'load_rails_models' do
|
|
47
75
|
before do
|
|
48
76
|
expect(Rails.application).to receive(:eager_load!)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: declare_schema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-12-
|
|
11
|
+
date: 2020-12-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -32,13 +32,13 @@ executables:
|
|
|
32
32
|
extensions: []
|
|
33
33
|
extra_rdoc_files: []
|
|
34
34
|
files:
|
|
35
|
-
- ".dependabot
|
|
35
|
+
- ".github/dependabot.yml"
|
|
36
|
+
- ".github/workflows/declare_schema_build.yml"
|
|
36
37
|
- ".github/workflows/gem_release.yml"
|
|
37
38
|
- ".gitignore"
|
|
38
39
|
- ".rspec"
|
|
39
40
|
- ".rubocop.yml"
|
|
40
41
|
- ".ruby-version"
|
|
41
|
-
- ".travis.yml"
|
|
42
42
|
- Appraisals
|
|
43
43
|
- CHANGELOG.md
|
|
44
44
|
- Gemfile
|
|
@@ -61,6 +61,7 @@ files:
|
|
|
61
61
|
- lib/declare_schema/model/field_spec.rb
|
|
62
62
|
- lib/declare_schema/model/foreign_key_definition.rb
|
|
63
63
|
- lib/declare_schema/model/index_definition.rb
|
|
64
|
+
- lib/declare_schema/model/table_options_definition.rb
|
|
64
65
|
- lib/declare_schema/railtie.rb
|
|
65
66
|
- lib/declare_schema/version.rb
|
|
66
67
|
- lib/generators/declare_schema/migration/USAGE
|
|
@@ -74,10 +75,12 @@ files:
|
|
|
74
75
|
- lib/generators/declare_schema/support/thor_shell.rb
|
|
75
76
|
- spec/lib/declare_schema/api_spec.rb
|
|
76
77
|
- spec/lib/declare_schema/field_declaration_dsl_spec.rb
|
|
78
|
+
- spec/lib/declare_schema/field_spec_spec.rb
|
|
77
79
|
- spec/lib/declare_schema/generator_spec.rb
|
|
78
80
|
- spec/lib/declare_schema/interactive_primary_key_spec.rb
|
|
79
81
|
- spec/lib/declare_schema/migration_generator_spec.rb
|
|
80
82
|
- spec/lib/declare_schema/model/index_definition_spec.rb
|
|
83
|
+
- spec/lib/declare_schema/model/table_options_definition_spec.rb
|
|
81
84
|
- spec/lib/declare_schema/prepare_testapp.rb
|
|
82
85
|
- spec/lib/generators/declare_schema/migration/migrator_spec.rb
|
|
83
86
|
- spec/spec_helper.rb
|
|
@@ -87,7 +90,7 @@ homepage: https://github.com/Invoca/declare_schema
|
|
|
87
90
|
licenses: []
|
|
88
91
|
metadata:
|
|
89
92
|
allowed_push_host: https://rubygems.org
|
|
90
|
-
post_install_message:
|
|
93
|
+
post_install_message:
|
|
91
94
|
rdoc_options: []
|
|
92
95
|
require_paths:
|
|
93
96
|
- lib
|
|
@@ -103,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
103
106
|
version: 1.3.6
|
|
104
107
|
requirements: []
|
|
105
108
|
rubygems_version: 3.0.3
|
|
106
|
-
signing_key:
|
|
109
|
+
signing_key:
|
|
107
110
|
specification_version: 4
|
|
108
111
|
summary: Database migration generator for Rails
|
|
109
112
|
test_files: []
|
data/.dependabot/config.yml
DELETED
data/.travis.yml
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
dist: trusty
|
|
3
|
-
os: linux
|
|
4
|
-
language: ruby
|
|
5
|
-
cache: bundler
|
|
6
|
-
rvm:
|
|
7
|
-
- 2.4.5
|
|
8
|
-
- 2.5.8
|
|
9
|
-
- 2.6.5
|
|
10
|
-
- 2.7.1
|
|
11
|
-
- ruby-head
|
|
12
|
-
gemfile:
|
|
13
|
-
- gemfiles/rails_4.gemfile
|
|
14
|
-
- gemfiles/rails_5.gemfile
|
|
15
|
-
- gemfiles/rails_6.gemfile
|
|
16
|
-
jobs:
|
|
17
|
-
fast_finish: false
|
|
18
|
-
exclude:
|
|
19
|
-
- gemfile: gemfiles/rails_4.gemfile
|
|
20
|
-
rvm: 2.7.1
|
|
21
|
-
- gemfile: gemfiles/rails_5.gemfile
|
|
22
|
-
rvm: 2.4.5
|
|
23
|
-
- gemfile: gemfiles/rails_6.gemfile
|
|
24
|
-
rvm: 2.4.5
|
|
25
|
-
allow_failures:
|
|
26
|
-
- rvm: ruby-head
|
|
27
|
-
before_install:
|
|
28
|
-
- rm -f /home/travis/.rvm/rubies/ruby-2.*/lib/ruby/gems/2.*/specifications/default/bundler-2.*.gemspec
|
|
29
|
-
- echo y | rvm @global do gem install bundler -v 1.17.3 --force --default
|
|
30
|
-
- gem install bundler -v 1.17.3 --force --default
|
|
31
|
-
- gem install bundler -v 1.17.3
|
|
32
|
-
install:
|
|
33
|
-
- bundle --version
|
|
34
|
-
- bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
|
|
35
|
-
script:
|
|
36
|
-
- bundle exec rake test:prepare_testapp[force]
|
|
37
|
-
- bundle exec rake test:all < test_responses.txt
|