declare_schema 0.6.1 → 0.7.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: c0108465514c80103fc934c550e911b563d4f3ebe8a1ebfb3a95c7fa10ed3ee8
4
- data.tar.gz: 60b586c698f62017311bef429896f79312314a61e918a1d6fb60dca60e4f6d15
3
+ metadata.gz: 6317ef2d7c038120278bbff6547c042a3418369d1e0bd8527f10ab57d58aef62
4
+ data.tar.gz: 66ce3ad03f6fde8365b287deb682d85b4fd7e983d283447af769aed0ade61571
5
5
  SHA512:
6
- metadata.gz: 2167096015bc12def6ed7f5ab3c45b1de223e79c9b3828d6a322c9a849d08534ac0551033058de9c6059733e344e7f9ab97ff152a11a67227e2490bc1726f022
7
- data.tar.gz: 173f38d4d7f7aa50904422624ddcec7a65f6f2b5e534b3eab5dd6894056d6166859a332d8ad9f021d5cc14306a6c0503c6a0308491bd5cede23c01934bd7c488
6
+ metadata.gz: 1a87ecf0479b94324f43c42d309f254e8a3ca10f3766cc90a756b94505cb559406871eee47a58fa376640ad85acda3e81d771cb03ab92dc08f5253d81bd88079
7
+ data.tar.gz: 4cd449cb9543819dd78e51267357f676a7439c89f2ad021f59a5d0f49664a8c8b8141eeebde4184e3eaf1f3f616dfef8a7092f59cc6382c73504de5ffeb41dc3
data/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ 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.7.1] - 2021-02-17
8
+ ### Fixed
9
+ - Exclude unknown options from FieldSpec#sql_options and #schema_attributes.
10
+ - Fixed a bug where fk_field_options were getting merged into spec_attrs after checking for equivalence,
11
+ leading to phantom migrations with no changes, or missing migrations when just the fk_field_options changed.
12
+
13
+ ## [0.7.0] - 2021-02-14
14
+ ### Changed
15
+ - Use `schema_attributes` for generating both up and down change migrations, so they are guaranteed to be symmetrical.
16
+ Note: Rails schema dumper is still used for the down migration to replace a model that has been dropped.
17
+
18
+ ## [0.6.4] - 2021-02-08
19
+ - Fixed a bug where the generated call to add_foreign_key() was not setting `column:`,
20
+ so it only worked in cases where Rails could infer the foreign key by convention.
21
+
22
+ ## [0.6.3] - 2021-01-21
23
+ ### Added
24
+ - Added `add_foreign_key` native rails call in `DeclareSchema::Model::ForeignKeyDefinition#to_add_statement`.
25
+
26
+ ### Fixed
27
+ - Fixed a bug in migration generation caused by `DeclareSchema::Migration#create_constraints`
28
+ calling `DeclareSchema::Model::ForeignKeyDefinition#to_add_statement` with unused parameters.
29
+
30
+ - Fixed a bug in `DeclareSchema::Migration#remove_foreign_key` where special characters would not be quoted properly.
31
+
32
+ ## [0.6.2] - 2021-01-06
33
+ ### Added
34
+ - Added `sqlite3` as dev dependency for local development
35
+
36
+ ### Fixed
37
+ - Fixed a bug in migration generation caused by `DeclareSchema::Model::ForeignKeyDefinition#to_add_statement`
38
+ not being passed proper arguments.
39
+
7
40
  ## [0.6.1] - 2021-01-06
8
41
  ### Added
9
42
  - Added Appraisals for MySQL as well as SQLite.
@@ -92,6 +125,11 @@ using the appropriate Rails configuration attributes.
92
125
  ### Added
93
126
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
94
127
 
128
+ [0.7.1]: https://github.com/Invoca/declare_schema/compare/v0.7.0...v0.7.1
129
+ [0.7.0]: https://github.com/Invoca/declare_schema/compare/v0.6.3...v0.7.0
130
+ [0.6.4]: https://github.com/Invoca/declare_schema/compare/v0.6.3...v0.6.4
131
+ [0.6.3]: https://github.com/Invoca/declare_schema/compare/v0.6.2...v0.6.3
132
+ [0.6.2]: https://github.com/Invoca/declare_schema/compare/v0.6.1...v0.6.2
95
133
  [0.6.1]: https://github.com/Invoca/declare_schema/compare/v0.6.0...v0.6.1
96
134
  [0.6.0]: https://github.com/Invoca/declare_schema/compare/v0.5.0...v0.6.0
97
135
  [0.5.0]: https://github.com/Invoca/declare_schema/compare/v0.4.2...v0.5.0
data/Gemfile CHANGED
@@ -19,3 +19,4 @@ gem 'responders'
19
19
  gem 'rspec'
20
20
  gem 'rubocop'
21
21
  gem 'yard'
22
+ gem 'sqlite3', '~> 1.4'
data/Gemfile.lock CHANGED
@@ -1,49 +1,49 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.6.1)
4
+ declare_schema (0.7.1)
5
5
  rails (>= 4.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actioncable (5.2.4.4)
11
- actionpack (= 5.2.4.4)
10
+ actioncable (5.2.4.5)
11
+ actionpack (= 5.2.4.5)
12
12
  nio4r (~> 2.0)
13
13
  websocket-driver (>= 0.6.1)
14
- actionmailer (5.2.4.4)
15
- actionpack (= 5.2.4.4)
16
- actionview (= 5.2.4.4)
17
- activejob (= 5.2.4.4)
14
+ actionmailer (5.2.4.5)
15
+ actionpack (= 5.2.4.5)
16
+ actionview (= 5.2.4.5)
17
+ activejob (= 5.2.4.5)
18
18
  mail (~> 2.5, >= 2.5.4)
19
19
  rails-dom-testing (~> 2.0)
20
- actionpack (5.2.4.4)
21
- actionview (= 5.2.4.4)
22
- activesupport (= 5.2.4.4)
20
+ actionpack (5.2.4.5)
21
+ actionview (= 5.2.4.5)
22
+ activesupport (= 5.2.4.5)
23
23
  rack (~> 2.0, >= 2.0.8)
24
24
  rack-test (>= 0.6.3)
25
25
  rails-dom-testing (~> 2.0)
26
26
  rails-html-sanitizer (~> 1.0, >= 1.0.2)
27
- actionview (5.2.4.4)
28
- activesupport (= 5.2.4.4)
27
+ actionview (5.2.4.5)
28
+ activesupport (= 5.2.4.5)
29
29
  builder (~> 3.1)
30
30
  erubi (~> 1.4)
31
31
  rails-dom-testing (~> 2.0)
32
32
  rails-html-sanitizer (~> 1.0, >= 1.0.3)
33
- activejob (5.2.4.4)
34
- activesupport (= 5.2.4.4)
33
+ activejob (5.2.4.5)
34
+ activesupport (= 5.2.4.5)
35
35
  globalid (>= 0.3.6)
36
- activemodel (5.2.4.4)
37
- activesupport (= 5.2.4.4)
38
- activerecord (5.2.4.4)
39
- activemodel (= 5.2.4.4)
40
- activesupport (= 5.2.4.4)
36
+ activemodel (5.2.4.5)
37
+ activesupport (= 5.2.4.5)
38
+ activerecord (5.2.4.5)
39
+ activemodel (= 5.2.4.5)
40
+ activesupport (= 5.2.4.5)
41
41
  arel (>= 9.0)
42
- activestorage (5.2.4.4)
43
- actionpack (= 5.2.4.4)
44
- activerecord (= 5.2.4.4)
42
+ activestorage (5.2.4.5)
43
+ actionpack (= 5.2.4.5)
44
+ activerecord (= 5.2.4.5)
45
45
  marcel (~> 0.3.1)
46
- activesupport (5.2.4.4)
46
+ activesupport (5.2.4.5)
47
47
  concurrent-ruby (~> 1.0, >= 1.0.2)
48
48
  i18n (>= 0.7, < 2)
49
49
  minitest (~> 5.1)
@@ -54,25 +54,25 @@ GEM
54
54
  thor (>= 0.14.0)
55
55
  arel (9.0.0)
56
56
  ast (2.4.1)
57
- bootsnap (1.5.1)
57
+ bootsnap (1.7.2)
58
58
  msgpack (~> 1.0)
59
59
  builder (3.2.4)
60
60
  byebug (11.1.3)
61
61
  climate_control (0.2.0)
62
62
  coderay (1.1.3)
63
- concurrent-ruby (1.1.7)
63
+ concurrent-ruby (1.1.8)
64
64
  crass (1.0.6)
65
65
  diff-lcs (1.4.4)
66
- erubi (1.9.0)
67
- ffi (1.13.1)
66
+ erubi (1.10.0)
67
+ ffi (1.14.2)
68
68
  globalid (0.4.2)
69
69
  activesupport (>= 4.2.0)
70
- i18n (1.8.5)
70
+ i18n (1.8.9)
71
71
  concurrent-ruby (~> 1.0)
72
- listen (3.3.1)
72
+ listen (3.4.1)
73
73
  rb-fsevent (~> 0.10, >= 0.10.3)
74
74
  rb-inotify (~> 0.9, >= 0.9.10)
75
- loofah (2.7.0)
75
+ loofah (2.9.0)
76
76
  crass (~> 1.0.2)
77
77
  nokogiri (>= 1.5.9)
78
78
  mail (2.7.1)
@@ -82,12 +82,13 @@ GEM
82
82
  method_source (1.0.0)
83
83
  mimemagic (0.3.5)
84
84
  mini_mime (1.0.2)
85
- mini_portile2 (2.4.0)
86
- minitest (5.14.2)
87
- msgpack (1.3.3)
88
- nio4r (2.5.4)
89
- nokogiri (1.10.10)
90
- mini_portile2 (~> 2.4.0)
85
+ mini_portile2 (2.5.0)
86
+ minitest (5.14.3)
87
+ msgpack (1.4.2)
88
+ nio4r (2.5.5)
89
+ nokogiri (1.11.1)
90
+ mini_portile2 (~> 2.5.0)
91
+ racc (~> 1.4)
91
92
  parallel (1.19.2)
92
93
  parser (2.7.1.4)
93
94
  ast (~> 2.4.1)
@@ -97,35 +98,36 @@ GEM
97
98
  pry-byebug (3.9.0)
98
99
  byebug (~> 11.0)
99
100
  pry (~> 0.13.0)
101
+ racc (1.5.2)
100
102
  rack (2.2.3)
101
103
  rack-test (1.1.0)
102
104
  rack (>= 1.0, < 3)
103
- rails (5.2.4.4)
104
- actioncable (= 5.2.4.4)
105
- actionmailer (= 5.2.4.4)
106
- actionpack (= 5.2.4.4)
107
- actionview (= 5.2.4.4)
108
- activejob (= 5.2.4.4)
109
- activemodel (= 5.2.4.4)
110
- activerecord (= 5.2.4.4)
111
- activestorage (= 5.2.4.4)
112
- activesupport (= 5.2.4.4)
105
+ rails (5.2.4.5)
106
+ actioncable (= 5.2.4.5)
107
+ actionmailer (= 5.2.4.5)
108
+ actionpack (= 5.2.4.5)
109
+ actionview (= 5.2.4.5)
110
+ activejob (= 5.2.4.5)
111
+ activemodel (= 5.2.4.5)
112
+ activerecord (= 5.2.4.5)
113
+ activestorage (= 5.2.4.5)
114
+ activesupport (= 5.2.4.5)
113
115
  bundler (>= 1.3.0)
114
- railties (= 5.2.4.4)
116
+ railties (= 5.2.4.5)
115
117
  sprockets-rails (>= 2.0.0)
116
118
  rails-dom-testing (2.0.3)
117
119
  activesupport (>= 4.2.0)
118
120
  nokogiri (>= 1.6)
119
121
  rails-html-sanitizer (1.3.0)
120
122
  loofah (~> 2.3)
121
- railties (5.2.4.4)
122
- actionpack (= 5.2.4.4)
123
- activesupport (= 5.2.4.4)
123
+ railties (5.2.4.5)
124
+ actionpack (= 5.2.4.5)
125
+ activesupport (= 5.2.4.5)
124
126
  method_source
125
127
  rake (>= 0.8.7)
126
128
  thor (>= 0.19.0, < 2.0)
127
129
  rainbow (3.0.0)
128
- rake (13.0.1)
130
+ rake (13.0.3)
129
131
  rb-fsevent (0.10.4)
130
132
  rb-inotify (0.10.1)
131
133
  ffi (~> 1.0)
@@ -166,15 +168,16 @@ GEM
166
168
  actionpack (>= 4.0)
167
169
  activesupport (>= 4.0)
168
170
  sprockets (>= 3.0.0)
169
- thor (1.0.1)
171
+ sqlite3 (1.4.2)
172
+ thor (1.1.0)
170
173
  thread_safe (0.3.6)
171
- tzinfo (1.2.7)
174
+ tzinfo (1.2.9)
172
175
  thread_safe (~> 0.1)
173
176
  unicode-display_width (1.7.0)
174
177
  websocket-driver (0.7.3)
175
178
  websocket-extensions (>= 0.1.0)
176
179
  websocket-extensions (0.1.5)
177
- yard (0.9.25)
180
+ yard (0.9.26)
178
181
 
179
182
  PLATFORMS
180
183
  ruby
@@ -192,6 +195,7 @@ DEPENDENCIES
192
195
  responders
193
196
  rspec
194
197
  rubocop
198
+ sqlite3 (~> 1.4)
195
199
  yard
196
200
 
197
201
  BUNDLED WITH
@@ -28,13 +28,12 @@ module DeclareSchema
28
28
  field(:lock_version, :integer, default: 1, null: false)
29
29
  end
30
30
 
31
- def field(name, type, *args)
32
- options = args.extract_options!
33
- @model.declare_field(name, type, *(args + [@options.merge(options)]))
31
+ def field(name, type, *args, **options)
32
+ @model.declare_field(name, type, *[*args, @options.merge(options)])
34
33
  end
35
34
 
36
35
  def method_missing(name, *args)
37
- field(name, args.first, *args[1..-1])
36
+ field(name, *args)
38
37
  end
39
38
  end
40
39
  end
@@ -81,8 +81,7 @@ module DeclareSchema
81
81
  # arguments. The arguments are forwarded to the #field_added
82
82
  # callback, allowing custom metadata to be added to field
83
83
  # declarations.
84
- def declare_field(name, type, *args)
85
- options = args.extract_options!
84
+ def declare_field(name, type, *args, **options)
86
85
  try(:field_added, name, type, args, options)
87
86
  add_serialize_for_field(name, type, options)
88
87
  add_formatting_for_field(name, type)
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeclareSchema
4
+ class UnknownSqlTypeError < RuntimeError; end
5
+
6
+ module Model
7
+ # This class is a wrapper for the ActiveRecord::...::Column class
8
+ class Column
9
+ class << self
10
+ def native_type?(type)
11
+ type != :primary_key && native_types.has_key?(type)
12
+ end
13
+
14
+ # MySQL example:
15
+ # { primary_key: "bigint auto_increment PRIMARY KEY",
16
+ # string: { name: "varchar", limit: 255 },
17
+ # text: { name: "text", limit: 65535},
18
+ # integer: {name: "int", limit: 4 },
19
+ # float: {name: "float", limit: 24 },
20
+ # decimal: { name: "decimal" },
21
+ # datetime: { name: "datetime" },
22
+ # timestamp: { name: "timestamp" },
23
+ # time: { name: "time" },
24
+ # date: { name: "date" },
25
+ # binary: { name>: "blob", limit: 65535 },
26
+ # boolean: { name: "tinyint", limit: 1 },
27
+ # json: { name: "json" } }
28
+ #
29
+ # SQLite example:
30
+ # { primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
31
+ # string: { name: "varchar" },
32
+ # text: { name: "text"},
33
+ # integer: { name: "integer" },
34
+ # float: { name: "float" },
35
+ # decimal: { name: "decimal" },
36
+ # datetime: { name: "datetime" },
37
+ # time: { name: "time" },
38
+ # date: { name: "date" },
39
+ # binary: { name: "blob" },
40
+ # boolean: { name: "boolean" },
41
+ # json: { name: "json" } }
42
+ def native_types
43
+ @native_types ||= ActiveRecord::Base.connection.native_database_types.tap do |types|
44
+ if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
45
+ types[:text][:limit] ||= 0xffff
46
+ types[:binary][:limit] ||= 0xffff
47
+ end
48
+ end
49
+ end
50
+
51
+ def sql_type(type)
52
+ if native_type?(type)
53
+ type
54
+ else
55
+ if (field_class = DeclareSchema.to_class(type))
56
+ field_class::COLUMN_TYPE
57
+ end or raise UnknownSqlTypeError, "#{type.inspect} for type #{type.inspect}"
58
+ end
59
+ end
60
+
61
+ def deserialize_default_value(column, sql_type, default_value)
62
+ sql_type or raise ArgumentError, "must pass sql_type; got #{sql_type.inspect}"
63
+
64
+ case Rails::VERSION::MAJOR
65
+ when 4
66
+ # TODO: Delete this Rails 4 support ASAP! This could be wrong, since it's using the type of the old column...which
67
+ # might be getting migrated to a new type. We should be using just sql_type as below. -Colin
68
+ column.type_cast_from_database(default_value)
69
+ else
70
+ cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, sql_type) or
71
+ raise "cast_type not found for #{sql_type}"
72
+ cast_type.deserialize(default_value)
73
+ end
74
+ end
75
+
76
+ # Normalizes schema attributes for the given database adapter name.
77
+ # Note that the un-normalized attributes are still useful for generating migrations because those
78
+ # may be run with a different adapter.
79
+ # This method never mutates its argument.
80
+ def normalize_schema_attributes(schema_attributes, db_adapter_name)
81
+ case schema_attributes[:type]
82
+ when :boolean
83
+ schema_attributes.reverse_merge(limit: 1)
84
+ when :integer
85
+ schema_attributes.reverse_merge(limit: 8) if db_adapter_name.match?(/sqlite/i)
86
+ when :float
87
+ schema_attributes.except(:limit)
88
+ when :text
89
+ schema_attributes.except(:limit) if db_adapter_name.match?(/sqlite/i)
90
+ when :datetime
91
+ schema_attributes.reverse_merge(precision: 0)
92
+ when NilClass
93
+ raise ArgumentError, ":type key not found; keys: #{schema_attributes.keys.inspect}"
94
+ end || schema_attributes
95
+ end
96
+
97
+ def equivalent_schema_attributes?(schema_attributes_lhs, schema_attributes_rhs)
98
+ db_adapter_name = ActiveRecord::Base.connection.class.name
99
+ normalized_lhs = normalize_schema_attributes(schema_attributes_lhs, db_adapter_name)
100
+ normalized_rhs = normalize_schema_attributes(schema_attributes_rhs, db_adapter_name)
101
+
102
+ normalized_lhs == normalized_rhs
103
+ end
104
+ end
105
+
106
+ attr_reader :sql_type
107
+
108
+ def initialize(model, current_table_name, column)
109
+ @model = model or raise ArgumentError, "must pass model"
110
+ @current_table_name = current_table_name or raise ArgumentError, "must pass current_table_name"
111
+ @column = column or raise ArgumentError, "must pass column"
112
+ @sql_type = self.class.sql_type(@column.type)
113
+ end
114
+
115
+ SCHEMA_KEYS = [:type, :limit, :precision, :scale, :null, :default].freeze
116
+
117
+ # omits keys with nil values
118
+ def schema_attributes
119
+ SCHEMA_KEYS.each_with_object({}) do |key, result|
120
+ value =
121
+ case key
122
+ when :default
123
+ self.class.deserialize_default_value(@column, @sql_type, @column.default)
124
+ else
125
+ col_value = @column.send(key)
126
+ if col_value.nil? && (native_type = self.class.native_types[@column.type])
127
+ native_type[key]
128
+ else
129
+ col_value
130
+ end
131
+ end
132
+
133
+ result[key] = value unless value.nil?
134
+ end.tap do |result|
135
+ if ActiveRecord::Base.connection.class.name.match?(/mysql/i) && @column.type.in?([:string, :text])
136
+ result.merge!(collation_and_charset_for_column(@current_table_name, @column.name))
137
+ end
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def collation_and_charset_for_column(current_table_name, column_name)
144
+ connection = ActiveRecord::Base.connection
145
+ connection.class.name.match?(/mysql/i) or raise ArgumentError, "only supported for MySQL"
146
+
147
+ database_name = connection.current_database
148
+
149
+ defaults = connection.select_one(<<~EOS)
150
+ SELECT C.character_set_name, C.collation_name
151
+ FROM information_schema.`COLUMNS` C
152
+ WHERE C.table_schema = '#{connection.quote_string(database_name)}' AND
153
+ C.table_name = '#{connection.quote_string(current_table_name)}' AND
154
+ C.column_name = '#{connection.quote_string(column_name)}';
155
+ EOS
156
+
157
+ defaults && defaults["character_set_name"] or raise "character_set_name missing from #{defaults.inspect} from #{database_name}.#{current_table_name}.#{column_name}"
158
+ defaults && defaults["collation_name"] or raise "collation_name missing from #{defaults.inspect}"
159
+
160
+ {
161
+ charset: defaults["character_set_name"],
162
+ collation: defaults["collation_name"]
163
+ }
164
+ end
165
+ end
166
+ end
167
+ end