declare_schema 0.7.0 → 0.8.0.pre.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 640e3580d2babdce916a3127b95cd1c84eb8d87b89ca67116496cd3729eea33c
4
- data.tar.gz: 30b86c95f516f37ce979ac558e64ac84f0150493392a0626495e83e68b5dc14d
3
+ metadata.gz: 296c7533718469d744f10a412c4a65b8da22ab1509a795fba4a3345fecfe2d99
4
+ data.tar.gz: cf6d5f65c95191a1e7976feec5c5def7291aff7abb2b22a0c78fa1184865c1e7
5
5
  SHA512:
6
- metadata.gz: 60e109345bc7d2551e441a7c5618f69b3e4df28551bc6c7807329f8831eb3090770fc70c2692487495935bd7c3394f83ccb77cfff1370afccb5a33105229f790
7
- data.tar.gz: 5f4cf408c53d963995795786ea847f6e5e4c7319fe91fd95d8eb6d4c05348ddb6cf1fcbbded15764e9b80af0e0903ec26b984d82c467a3a0a6956e421f7d5ace
6
+ metadata.gz: c597b1b7d371ed218a36a6bd63bcb401a9c482b45e1b549a3ff0b18a1eefa9bef749de95804a9da4401a13e2e09178639942cd068ef0f4ec02f7c816b9175087
7
+ data.tar.gz: 1cc1dd96b29b84b9a1d2397db7fbbf68bb72ec1c471d6a029a786906f3e402336db17e5f8af910ca36d54cdd6c3eb652d8a18ebf149cdb87719437fd0011c06c
data/CHANGELOG.md CHANGED
@@ -4,16 +4,27 @@ 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.0] - 2020-02-14
7
+ ## [0.8.0] - UNRELEASED
8
+ ### Removed
9
+ - Removed `sql_type` that was confusing because it was actually the same as `type` (ex: :string) and not
10
+ in fact the SQL type (ex: ``varchar(255)'`).
11
+
12
+ ## [0.7.1] - 2021-02-17
13
+ ### Fixed
14
+ - Exclude unknown options from FieldSpec#sql_options and #schema_attributes.
15
+ - Fixed a bug where fk_field_options were getting merged into spec_attrs after checking for equivalence,
16
+ leading to phantom migrations with no changes, or missing migrations when just the fk_field_options changed.
17
+
18
+ ## [0.7.0] - 2021-02-14
8
19
  ### Changed
9
20
  - Use `schema_attributes` for generating both up and down change migrations, so they are guaranteed to be symmetrical.
10
21
  Note: Rails schema dumper is still used for the down migration to replace a model that has been dropped.
11
22
 
12
- ## [0.6.4] - 2020-02-08
23
+ ## [0.6.4] - 2021-02-08
13
24
  - Fixed a bug where the generated call to add_foreign_key() was not setting `column:`,
14
25
  so it only worked in cases where Rails could infer the foreign key by convention.
15
26
 
16
- ## [0.6.3] - 2020-01-21
27
+ ## [0.6.3] - 2021-01-21
17
28
  ### Added
18
29
  - Added `add_foreign_key` native rails call in `DeclareSchema::Model::ForeignKeyDefinition#to_add_statement`.
19
30
 
@@ -119,6 +130,8 @@ using the appropriate Rails configuration attributes.
119
130
  ### Added
120
131
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
121
132
 
133
+ [0.8.0]: https://github.com/Invoca/declare_schema/compare/v0.7.1...v0.8.0
134
+ [0.7.1]: https://github.com/Invoca/declare_schema/compare/v0.7.0...v0.7.1
122
135
  [0.7.0]: https://github.com/Invoca/declare_schema/compare/v0.6.3...v0.7.0
123
136
  [0.6.4]: https://github.com/Invoca/declare_schema/compare/v0.6.3...v0.6.4
124
137
  [0.6.3]: https://github.com/Invoca/declare_schema/compare/v0.6.2...v0.6.3
data/Gemfile.lock CHANGED
@@ -1,49 +1,49 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.7.0)
4
+ declare_schema (0.8.0.pre.4)
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)
@@ -60,19 +60,19 @@ GEM
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)
66
+ erubi (1.10.0)
67
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
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)
85
+ mini_portile2 (2.5.0)
86
+ minitest (5.14.3)
87
87
  msgpack (1.4.2)
88
- nio4r (2.5.4)
89
- nokogiri (1.10.10)
90
- mini_portile2 (~> 2.4.0)
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)
@@ -167,9 +169,9 @@ GEM
167
169
  activesupport (>= 4.0)
168
170
  sprockets (>= 3.0.0)
169
171
  sqlite3 (1.4.2)
170
- thor (1.0.1)
172
+ thor (1.1.0)
171
173
  thread_safe (0.3.6)
172
- tzinfo (1.2.7)
174
+ tzinfo (1.2.9)
173
175
  thread_safe (~> 0.1)
174
176
  unicode-display_width (1.7.0)
175
177
  websocket-driver (0.7.3)
@@ -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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rails'
4
+
3
5
  require 'declare_schema/extensions/module'
4
6
 
5
7
  module DeclareSchema
@@ -81,8 +83,7 @@ module DeclareSchema
81
83
  # arguments. The arguments are forwarded to the #field_added
82
84
  # callback, allowing custom metadata to be added to field
83
85
  # declarations.
84
- def declare_field(name, type, *args)
85
- options = args.extract_options!
86
+ def declare_field(name, type, *args, **options)
86
87
  try(:field_added, name, type, args, options)
87
88
  add_serialize_for_field(name, type, options)
88
89
  add_formatting_for_field(name, type)
@@ -100,8 +101,29 @@ module DeclareSchema
100
101
  end
101
102
  end
102
103
 
103
- def primary_key
104
- super || 'id'
104
+ if ::Rails::VERSION::MAJOR < 5
105
+ def primary_key
106
+ super || 'id'
107
+ end
108
+ end
109
+
110
+ # returns the primary key (String) as declared with primary_key =
111
+ # unlike the `primary_key` method, DOES NOT query the database to find the actual primary key in use right now
112
+ # if no explicit primary key set, returns the default_defined_primary_key
113
+ def defined_primary_key
114
+ if defined?(@primary_key)
115
+ @primary_key&.to_s
116
+ end || default_defined_primary_key
117
+ end
118
+
119
+ # if this is a derived class, returns the base class's defined_primary_key
120
+ # otherwise, returns 'id'
121
+ def default_defined_primary_key
122
+ if self == base_class
123
+ 'id'
124
+ else
125
+ base_class.defined_primary_key
126
+ end
105
127
  end
106
128
 
107
129
  private
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- class UnknownSqlTypeError < RuntimeError; end
4
+ class UnknownTypeError < RuntimeError; end
5
5
 
6
6
  module Model
7
7
  # This class is a wrapper for the ActiveRecord::...::Column class
8
8
  class Column
9
9
  class << self
10
10
  def native_type?(type)
11
- type != :primary_key && native_types.has_key?(type)
11
+ type != :primary_key && (native_types.empty? || native_types[type]) # empty will happen with NullDBAdapter used in assets:precompile
12
12
  end
13
13
 
14
14
  # MySQL example:
@@ -48,69 +48,59 @@ module DeclareSchema
48
48
  end
49
49
  end
50
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}"
51
+ def deserialize_default_value(column, type, default_value)
52
+ type or raise ArgumentError, "must pass type; got #{type.inspect}"
63
53
 
64
54
  case Rails::VERSION::MAJOR
65
55
  when 4
66
56
  # 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
57
+ # might be getting migrated to a new type. We should be using just type as below. -Colin
68
58
  column.type_cast_from_database(default_value)
69
59
  else
70
- cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, sql_type) or
71
- raise "cast_type not found for #{sql_type}"
60
+ cast_type = ActiveRecord::Base.connection.send(:lookup_cast_type, type) or
61
+ raise "cast_type not found for #{type}"
72
62
  cast_type.deserialize(default_value)
73
63
  end
74
64
  end
75
65
 
76
- # Normalizes schema attributes for the specific database adapter that is currently running
66
+ # Normalizes schema attributes for the given database adapter name.
77
67
  # Note that the un-normalized attributes are still useful for generating migrations because those
78
68
  # may be run with a different adapter.
79
- # This method never mutates its argument. In fact it freezes it to be certain.
80
- def normalize_schema_attributes(schema_attributes)
81
- schema_attributes[:type] or raise ArgumentError, ":type key not found; keys: #{schema_attributes.keys.inspect}"
82
- schema_attributes.freeze
83
-
84
- case ActiveRecord::Base.connection.class.name
85
- when /mysql/i
86
- schema_attributes
87
- when /sqlite/i
88
- case schema_attributes[:type]
89
- when :text
90
- schema_attributes = schema_attributes.merge(limit: nil)
91
- when :integer
92
- schema_attributes = schema_attributes.dup
93
- schema_attributes[:limit] ||= 8
94
- end
95
- schema_attributes
96
- else
97
- schema_attributes
98
- end
69
+ # This method never mutates its argument.
70
+ def normalize_schema_attributes(schema_attributes, db_adapter_name)
71
+ case schema_attributes[:type]
72
+ when :boolean
73
+ schema_attributes.reverse_merge(limit: 1)
74
+ when :integer
75
+ schema_attributes.reverse_merge(limit: 8) if db_adapter_name.match?(/sqlite/i)
76
+ when :float
77
+ schema_attributes.except(:limit)
78
+ when :text
79
+ schema_attributes.except(:limit) if db_adapter_name.match?(/sqlite/i)
80
+ when :datetime
81
+ schema_attributes.reverse_merge(precision: 0)
82
+ when NilClass
83
+ raise ArgumentError, ":type key not found; keys: #{schema_attributes.keys.inspect}"
84
+ end || schema_attributes
99
85
  end
100
86
 
101
87
  def equivalent_schema_attributes?(schema_attributes_lhs, schema_attributes_rhs)
102
- normalize_schema_attributes(schema_attributes_lhs) == normalize_schema_attributes(schema_attributes_rhs)
88
+ db_adapter_name = ActiveRecord::Base.connection.class.name
89
+ normalized_lhs = normalize_schema_attributes(schema_attributes_lhs, db_adapter_name)
90
+ normalized_rhs = normalize_schema_attributes(schema_attributes_rhs, db_adapter_name)
91
+
92
+ normalized_lhs == normalized_rhs
103
93
  end
104
94
  end
105
95
 
96
+ attr_reader :type
97
+
106
98
  def initialize(model, current_table_name, column)
107
99
  @model = model or raise ArgumentError, "must pass model"
108
100
  @current_table_name = current_table_name or raise ArgumentError, "must pass current_table_name"
109
101
  @column = column or raise ArgumentError, "must pass column"
110
- end
111
-
112
- def sql_type
113
- @sql_type ||= self.class.sql_type(@column.type)
102
+ @type = @column.type
103
+ self.class.native_type?(@type) or raise UnknownTypeError, "#{@type.inspect}"
114
104
  end
115
105
 
116
106
  SCHEMA_KEYS = [:type, :limit, :precision, :scale, :null, :default].freeze
@@ -121,7 +111,7 @@ module DeclareSchema
121
111
  value =
122
112
  case key
123
113
  when :default
124
- self.class.deserialize_default_value(@column, sql_type, @column.default)
114
+ self.class.deserialize_default_value(@column, @type, @column.default)
125
115
  else
126
116
  col_value = @column.send(key)
127
117
  if col_value.nil? && (native_type = self.class.native_types[@column.type])
@@ -33,7 +33,7 @@ module DeclareSchema
33
33
  end
34
34
  end
35
35
 
36
- attr_reader :model, :name, :type, :sql_type, :position, :options, :sql_options
36
+ attr_reader :model, :name, :type, :position, :options, :sql_options
37
37
 
38
38
  TYPE_SYNONYMS = { timestamp: :datetime }.freeze # TODO: drop this synonym. -Colin
39
39
 
@@ -47,11 +47,9 @@ module DeclareSchema
47
47
  end
48
48
 
49
49
  def initialize(model, name, type, position: 0, **options)
50
- # TODO: TECH-5116
51
- # Invoca change - searching for the primary key was causing an additional database read on every model load. Assume
52
- # "id" which works for invoca.
53
- # raise ArgumentError, "you cannot provide a field spec for the primary key" if name == model.primary_key
54
- name == "id" and raise ArgumentError, "you cannot provide a field spec for the primary key"
50
+ defined_primary_key = model.defined_primary_key
51
+
52
+ name.to_s == defined_primary_key and raise ArgumentError, "you may not provide a field spec for the primary key #{name.inspect}"
55
53
 
56
54
  @model = model
57
55
  @name = name.to_sym
@@ -62,13 +60,13 @@ module DeclareSchema
62
60
 
63
61
  @options.has_key?(:null) or @options[:null] = false
64
62
 
65
- case type
63
+ case @type
66
64
  when :text
67
65
  if self.class.mysql_text_limits?
68
66
  @options[:default].nil? or raise MysqlTextMayNotHaveDefault, "when using MySQL, non-nil default may not be given for :text field #{model}##{@name}"
69
67
  @options[:limit] = self.class.round_up_mysql_text_limit(@options[:limit] || MYSQL_LONGTEXT_LIMIT)
70
68
  else
71
- @options[:limit] = nil
69
+ @options.delete(:limit)
72
70
  end
73
71
  when :string
74
72
  @options[:limit] or raise "limit: must be given for :string field #{model}##{@name}: #{@options.inspect}; do you want `limit: 255`?"
@@ -77,24 +75,23 @@ module DeclareSchema
77
75
  @options[:limit] = 8
78
76
  end
79
77
 
80
- # TODO: Do we really need to support a :sql_type option? Ideally, drop it. -Colin
81
- @sql_type = @options.delete(:sql_type) || Column.sql_type(@type)
78
+ Column.native_type?(@type) or raise UnknownTypeError, "#{@type.inspect} not found in #{Column.native_types.inspect} for adapter #{ActiveRecord::Base.connection.class.name}"
82
79
 
83
- if @sql_type.in?([:string, :text, :binary, :varbinary, :integer, :enum])
84
- @options[:limit] ||= Column.native_types[@sql_type][:limit]
80
+ if @type.in?([:string, :text, :binary, :varbinary, :integer, :enum])
81
+ @options[:limit] ||= Column.native_types[@type][:limit]
85
82
  else
86
- @sql_type != :decimal && @options.has_key?(:limit) and warn("unsupported limit: for SQL type #{@sql_type} in field #{model}##{@name}")
83
+ @type != :decimal && @options.has_key?(:limit) and warn("unsupported limit: for SQL type #{@type} in field #{model}##{@name}")
87
84
  @options.delete(:limit)
88
85
  end
89
86
 
90
- if @sql_type == :decimal
87
+ if @type == :decimal
91
88
  @options[:precision] or warn("precision: required for :decimal type in field #{model}##{@name}")
92
89
  @options[:scale] or warn("scale: required for :decimal type in field #{model}##{@name}")
93
90
  else
94
- if @sql_type != :datetime
95
- @options.has_key?(:precision) and warn("precision: only allowed for :decimal type or :datetime for SQL type #{@sql_type} in field #{model}##{@name}")
91
+ if @type != :datetime
92
+ @options.has_key?(:precision) and warn("precision: only allowed for :decimal type or :datetime for SQL type #{@type} in field #{model}##{@name}")
96
93
  end
97
- @options.has_key?(:scale) and warn("scale: only allowed for :decimal type for SQL type #{@sql_type} in field #{model}##{@name}")
94
+ @options.has_key?(:scale) and warn("scale: only allowed for :decimal type for SQL type #{@type} in field #{model}##{@name}")
98
95
  end
99
96
 
100
97
  if @type.in?([:text, :string])
@@ -106,21 +103,21 @@ module DeclareSchema
106
103
  @options.delete(:collation)
107
104
  end
108
105
  else
109
- @options[:charset] and warn("charset may only given for :string and :text fields for SQL type #{@sql_type} in field #{model}##{@name}")
110
- @options[:collation] and warne("collation may only given for :string and :text fields for SQL type #{@sql_type} in field #{model}##{@name}")
106
+ @options[:charset] and warn("charset may only given for :string and :text fields for SQL type #{@type} in field #{model}##{@name}")
107
+ @options[:collation] and warne("collation may only given for :string and :text fields for SQL type #{@type} in field #{model}##{@name}")
111
108
  end
112
109
 
113
110
  @options = Hash[@options.sort_by { |k, _v| OPTION_INDEXES[k] || 9999 }]
114
111
 
115
- @sql_options = @options.except(*NON_SQL_OPTIONS)
112
+ @sql_options = @options.slice(*SQL_OPTIONS)
116
113
  end
117
114
 
118
115
  # returns the attributes for schema migrations as a Hash
119
116
  # omits name and position since those are meta-data above the schema
120
117
  # omits keys with nil values
121
118
  def schema_attributes(col_spec)
122
- @options.merge(type: @type).tap do |attrs|
123
- attrs[:default] = Column.deserialize_default_value(col_spec, @sql_type, attrs[:default])
119
+ @sql_options.merge(type: @type).tap do |attrs|
120
+ attrs[:default] = Column.deserialize_default_value(col_spec, @type, attrs[:default])
124
121
  end.compact
125
122
  end
126
123
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'index_definition'
4
+
3
5
  module DeclareSchema
4
6
  module Model
5
7
  class ForeignKeyDefinition
@@ -15,10 +17,10 @@ module DeclareSchema
15
17
  @child_table = model.table_name # unless a table rename, which would happen when a class is renamed??
16
18
  @parent_table_name = options[:parent_table]&.to_s
17
19
  @foreign_key_name = options[:foreign_key]&.to_s || @foreign_key
18
- @index_name = options[:index_name]&.to_s || model.connection.index_name(model.table_name, column: @foreign_key_name)
19
20
 
20
- # Empty constraint lets mysql generate the name
21
- @constraint_name = options[:constraint_name]&.to_s || @index_name&.to_s || ''
21
+ @constraint_name = options[:constraint_name]&.to_s ||
22
+ options[:index_name]&.to_s ||
23
+ IndexDefinition.index_name(@foreign_key_name)
22
24
  @on_delete_cascade = options[:dependent] == :delete
23
25
  end
24
26
 
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeclareSchema
4
+ module Model
5
+ class HabtmModelShim
6
+ class << self
7
+ def from_reflection(refl)
8
+ join_table = refl.join_table
9
+ foreign_keys_and_classes = [
10
+ [refl.foreign_key.to_s, refl.active_record],
11
+ [refl.association_foreign_key.to_s, refl.class_name.constantize]
12
+ ].sort { |a, b| a.first <=> b.first }
13
+ foreign_keys = foreign_keys_and_classes.map(&:first)
14
+ foreign_key_classes = foreign_keys_and_classes.map(&:last)
15
+ # this may fail in weird ways if HABTM is running across two DB connections (assuming that's even supported)
16
+ # figure that anybody who sets THAT up can deal with their own migrations...
17
+ connection = refl.active_record.connection
18
+
19
+ new(join_table, foreign_keys, foreign_key_classes, connection)
20
+ end
21
+ end
22
+
23
+ attr_reader :join_table, :foreign_keys, :foreign_key_classes, :connection
24
+
25
+ def initialize(join_table, foreign_keys, foreign_key_classes, connection)
26
+ @join_table = join_table
27
+ @foreign_keys = foreign_keys
28
+ @foreign_key_classes = foreign_key_classes
29
+ @connection = connection
30
+ end
31
+
32
+ def table_options
33
+ {}
34
+ end
35
+
36
+ def table_name
37
+ join_table
38
+ end
39
+
40
+ def field_specs
41
+ foreign_keys.each_with_index.each_with_object({}) do |(v, position), result|
42
+ result[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :integer, position: position, null: false)
43
+ end
44
+ end
45
+
46
+ def primary_key
47
+ false # no single-column primary key in database
48
+ end
49
+
50
+ def defined_primary_key
51
+ false # no single-column primary key declared
52
+ end
53
+
54
+ def index_definitions_with_primary_key
55
+ [
56
+ IndexDefinition.new(self, foreign_keys, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME),
57
+ IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
58
+ ]
59
+ end
60
+
61
+ alias_method :index_definitions, :index_definitions_with_primary_key
62
+
63
+ def ignore_indexes
64
+ []
65
+ end
66
+
67
+ def constraint_specs
68
+ [
69
+ ForeignKeyDefinition.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
70
+ ForeignKeyDefinition.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
71
+ ]
72
+ end
73
+ end
74
+ end
75
+ end
@@ -19,7 +19,7 @@ module DeclareSchema
19
19
  @table = options.delete(:table_name) || model.table_name
20
20
  @fields = Array.wrap(fields).map(&:to_s)
21
21
  @explicit_name = options[:name] unless options.delete(:allow_equivalent)
22
- @name = options.delete(:name) || model.connection.index_name(table, column: @fields).gsub(/index.*_on_/, 'on_')
22
+ @name = options.delete(:name) || self.class.index_name(@fields)
23
23
  @unique = options.delete(:unique) || name == PRIMARY_KEY_NAME || false
24
24
 
25
25
  if @name.length > MYSQL_INDEX_NAME_MAX_LENGTH
@@ -60,6 +60,10 @@ module DeclareSchema
60
60
  index_definitions
61
61
  end
62
62
 
63
+ def index_name(columns)
64
+ "on_#{Array(columns).join("_and_")}"
65
+ end
66
+
63
67
  private
64
68
 
65
69
  # This is the old approach which is still needed for MySQL in Rails 4 and SQLite
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0.pre.4"
5
5
  end
@@ -96,7 +96,7 @@ module DeclareSchema
96
96
  end
97
97
  end
98
98
  end
99
- rescue ::DeclareSchema::UnknownSqlTypeError => ex
99
+ rescue ::DeclareSchema::UnknownTypeError => ex
100
100
  say "Invalid field type: #{ex}"
101
101
  end
102
102
 
@@ -6,69 +6,6 @@ require 'active_record/connection_adapters/abstract_adapter'
6
6
  module Generators
7
7
  module DeclareSchema
8
8
  module Migration
9
- HabtmModelShim = Struct.new(:join_table, :foreign_keys, :foreign_key_classes, :connection) do
10
- class << self
11
- def from_reflection(refl)
12
- join_table = refl.join_table
13
- foreign_keys_and_classes = [
14
- [refl.foreign_key.to_s, refl.active_record],
15
- [refl.association_foreign_key.to_s, refl.class_name.constantize]
16
- ].sort { |a, b| a.first <=> b.first }
17
- foreign_keys = foreign_keys_and_classes.map(&:first)
18
- foreign_key_classes = foreign_keys_and_classes.map(&:last)
19
- # this may fail in weird ways if HABTM is running across two DB connections (assuming that's even supported)
20
- # figure that anybody who sets THAT up can deal with their own migrations...
21
- connection = refl.active_record.connection
22
-
23
- new(join_table, foreign_keys, foreign_key_classes, connection)
24
- end
25
- end
26
-
27
- def table_options
28
- {}
29
- end
30
-
31
- def table_name
32
- join_table
33
- end
34
-
35
- def table_exists?
36
- ActiveRecord::Migration.table_exists? table_name
37
- end
38
-
39
- def field_specs
40
- i = 0
41
- foreign_keys.each_with_object({}) do |v, result|
42
- result[v] = ::DeclareSchema::Model::FieldSpec.new(self, v, :integer, position: i, null: false)
43
- i += 1
44
- end
45
- end
46
-
47
- def primary_key
48
- false # no single-column primary key
49
- end
50
-
51
- def index_definitions_with_primary_key
52
- [
53
- ::DeclareSchema::Model::IndexDefinition.new(self, foreign_keys, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME),
54
- ::DeclareSchema::Model::IndexDefinition.new(self, foreign_keys.last) # not unique by itself; combines with primary key to be unique
55
- ]
56
- end
57
-
58
- alias_method :index_definitions, :index_definitions_with_primary_key
59
-
60
- def ignore_indexes
61
- []
62
- end
63
-
64
- def constraint_specs
65
- [
66
- ::DeclareSchema::Model::ForeignKeyDefinition.new(self, foreign_keys.first, parent_table: foreign_key_classes.first.table_name, constraint_name: "#{join_table}_FK1", dependent: :delete),
67
- ::DeclareSchema::Model::ForeignKeyDefinition.new(self, foreign_keys.last, parent_table: foreign_key_classes.last.table_name, constraint_name: "#{join_table}_FK2", dependent: :delete)
68
- ]
69
- end
70
- end
71
-
72
9
  class Migrator
73
10
  class Error < RuntimeError; end
74
11
 
@@ -266,7 +203,7 @@ module Generators
266
203
  end
267
204
  # generate shims for HABTM models
268
205
  habtm_tables.each do |name, refls|
269
- models_by_table_name[name] = HabtmModelShim.from_reflection(refls.first)
206
+ models_by_table_name[name] = ::DeclareSchema::Model::HabtmModelShim.from_reflection(refls.first)
270
207
  end
271
208
  model_table_names = models_by_table_name.keys
272
209
 
@@ -328,7 +265,7 @@ module Generators
328
265
  end
329
266
 
330
267
  def create_table(model)
331
- longest_field_name = model.field_specs.values.map { |f| f.sql_type.to_s.length }.max
268
+ longest_field_name = model.field_specs.values.map { |f| f.type.to_s.length }.max
332
269
  disable_auto_increment = model.respond_to?(:disable_auto_increment) && model.disable_auto_increment
333
270
  table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
334
271
  field_definitions = [
@@ -379,7 +316,7 @@ module Generators
379
316
  def create_field(field_spec, field_name_width)
380
317
  options = field_spec.sql_options.merge(fk_field_options(field_spec.model, field_spec.name))
381
318
  args = [field_spec.name.inspect] + format_options(options.compact)
382
- format("t.%-*s %s", field_name_width, field_spec.sql_type, args.join(', '))
319
+ format("t.%-*s %s", field_name_width, field_spec.type, args.join(', '))
383
320
  end
384
321
 
385
322
  def change_table(model, current_table_name)
@@ -417,7 +354,7 @@ module Generators
417
354
  args =
418
355
  if (spec = model.field_specs[c])
419
356
  options = spec.sql_options.merge(fk_field_options(model, c))
420
- [":#{spec.sql_type}", *format_options(options.compact)]
357
+ [":#{spec.type}", *format_options(options.compact)]
421
358
  else
422
359
  [":integer"]
423
360
  end
@@ -444,12 +381,12 @@ module Generators
444
381
  spec_attrs = spec.schema_attributes(column)
445
382
  column_declaration = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
446
383
  col_attrs = column_declaration.schema_attributes
447
- if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(spec_attrs, col_attrs)
448
- normalized_schema_attributes = spec_attrs.merge(fk_field_options(model, col_name_to_change))
384
+ normalized_schema_attrs = spec_attrs.merge(fk_field_options(model, col_name_to_change))
449
385
 
450
- type = normalized_schema_attributes.delete(:type) or raise "no :type found in #{normalized_schema_attributes.inspect}"
386
+ if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
387
+ type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
451
388
  changes << ["change_column #{new_table_name.to_sym.inspect}", col_name_to_change.to_sym.inspect,
452
- type.to_sym.inspect, *format_options(normalized_schema_attributes)].join(", ")
389
+ type.to_sym.inspect, *format_options(normalized_schema_attrs)].join(", ")
453
390
  undo_changes << change_column_back(model, current_table_name, orig_col_name)
454
391
  end
455
392
  end
@@ -6,8 +6,8 @@ rescue LoadError
6
6
  end
7
7
 
8
8
  RSpec.describe DeclareSchema::Model::FieldSpec do
9
- let(:model) { double('model', table_options: {}) }
10
- let(:col_spec) { double('col_spec', sql_type: 'varchar') }
9
+ let(:model) { double('model', table_options: {}, defined_primary_key: 'id') }
10
+ let(:col_spec) { double('col_spec', type: :string) }
11
11
 
12
12
  before do
13
13
  load File.expand_path('prepare_testapp.rb', __dir__)
@@ -22,6 +22,12 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
22
22
  subject = described_class.new(model, :price, :integer, anonymize_using: 'x', null: false, position: 0, limit: 4)
23
23
  expect(subject.options.keys).to eq([:limit, :null, :anonymize_using])
24
24
  end
25
+
26
+ it 'raises exception on unknown field type' do
27
+ expect do
28
+ described_class.new(model, :location, :lat_long, position: 0)
29
+ end.to raise_exception(::DeclareSchema::UnknownTypeError, /:lat_long not found in /)
30
+ end
25
31
  end
26
32
 
27
33
  describe '#schema_attributes' do
@@ -127,7 +133,7 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
127
133
  end
128
134
 
129
135
  describe 'default:' do
130
- let(:col_spec) { double('col_spec', sql_type: :integer) }
136
+ let(:col_spec) { double('col_spec', type: :integer) }
131
137
 
132
138
  it 'typecasts default value' do
133
139
  allow(col_spec).to receive(:type_cast_from_database) { |default| Integer(default) }
@@ -149,8 +155,8 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
149
155
  end
150
156
  end
151
157
 
152
- it 'returns the attributes except name, position' do
153
- subject = described_class.new(model, :price, :bigint, null: true, default: 0, position: 2)
158
+ it 'returns the attributes except name, position, and non-SQL options' do
159
+ subject = described_class.new(model, :price, :bigint, null: true, default: 0, ruby_default: -> { }, encrypt_using: -> { }, position: 2)
154
160
  expect(subject.schema_attributes(col_spec)).to eq(type: :integer, limit: 8, null: true, default: 0)
155
161
  end
156
162
 
@@ -163,4 +169,11 @@ RSpec.describe DeclareSchema::Model::FieldSpec do
163
169
  expect(bigint.schema_attributes(col_spec)).to eq(expected_attributes)
164
170
  end
165
171
  end
172
+
173
+ describe '#sql_options' do
174
+ subject { described_class.new(model, :price, :integer, limit: 4, null: true, default: 0, position: 2, encrypt_using: ->(field) { field }) }
175
+ it 'excludes non-sql options' do
176
+ expect(subject.sql_options).to eq(limit: 4, null: true, default: 0)
177
+ end
178
+ end
166
179
  end
@@ -372,14 +372,14 @@ RSpec.describe 'DeclareSchema Migration Generator' do
372
372
 
373
373
  add_index :adverts, [:category_id], name: 'on_category_id'
374
374
 
375
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" if defined?(Mysql2)}
375
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" if defined?(Mysql2)}
376
376
  EOS
377
377
  .and migrate_down(<<~EOS.strip)
378
378
  remove_column :adverts, :category_id
379
379
 
380
380
  remove_index :adverts, name: :on_category_id rescue ActiveRecord::StatementInvalid
381
381
 
382
- #{"remove_foreign_key(\"adverts\", name: \"index_adverts_on_category_id\")\n" if defined?(Mysql2)}
382
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" if defined?(Mysql2)}
383
383
  EOS
384
384
  )
385
385
 
@@ -400,8 +400,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
400
400
 
401
401
  add_index :adverts, [:c_id], name: 'on_c_id'
402
402
 
403
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
404
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
403
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
404
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
405
405
  EOS
406
406
  )
407
407
 
@@ -420,8 +420,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
420
420
  migrate_up(<<~EOS.strip)
421
421
  add_column :adverts, :category_id, :integer, limit: 8, null: false
422
422
 
423
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
424
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
423
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
424
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
425
425
  EOS
426
426
  )
427
427
 
@@ -442,8 +442,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
442
442
 
443
443
  add_index :adverts, [:category_id], name: 'my_index'
444
444
 
445
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
446
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
445
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
446
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
447
447
  EOS
448
448
  )
449
449
 
@@ -468,16 +468,16 @@ RSpec.describe 'DeclareSchema Migration Generator' do
468
468
  add_column :adverts, :updated_at, :datetime, null: true
469
469
  add_column :adverts, :lock_version, :integer#{lock_version_limit}, null: false, default: 1
470
470
 
471
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
472
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
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
473
  EOS
474
474
  .and migrate_down(<<~EOS.strip)
475
475
  remove_column :adverts, :created_at
476
476
  remove_column :adverts, :updated_at
477
477
  remove_column :adverts, :lock_version
478
478
 
479
- #{"remove_foreign_key(\"adverts\", name: \"index_adverts_on_category_id\")\n" +
480
- "remove_foreign_key(\"adverts\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
479
+ #{"remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
480
+ "remove_foreign_key(\"adverts\", name: \"on_c_id\")" if defined?(Mysql2)}
481
481
  EOS
482
482
  )
483
483
 
@@ -501,8 +501,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
501
501
 
502
502
  add_index :adverts, [:title], name: 'on_title'
503
503
 
504
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
505
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
504
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
505
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
506
506
  EOS
507
507
  )
508
508
 
@@ -522,8 +522,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
522
522
 
523
523
  add_index :adverts, [:title], unique: true, name: 'on_title'
524
524
 
525
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
526
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
525
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
526
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
527
527
  EOS
528
528
  )
529
529
 
@@ -543,8 +543,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
543
543
 
544
544
  add_index :adverts, [:title], name: 'my_index'
545
545
 
546
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
547
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
546
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
547
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
548
548
  EOS
549
549
  )
550
550
 
@@ -562,8 +562,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
562
562
 
563
563
  add_index :adverts, [:title], name: 'on_title'
564
564
 
565
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
566
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
565
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
566
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
567
567
  EOS
568
568
  )
569
569
 
@@ -581,8 +581,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
581
581
 
582
582
  add_index :adverts, [:title], unique: true, name: 'my_index'
583
583
 
584
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
585
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
584
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
585
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
586
586
  EOS
587
587
  )
588
588
 
@@ -600,8 +600,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
600
600
 
601
601
  add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
602
602
 
603
- #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
604
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")" if defined?(Mysql2)}
603
+ #{"add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"on_category_id\")\n" +
604
+ "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"on_c_id\")" if defined?(Mysql2)}
605
605
  EOS
606
606
  )
607
607
 
@@ -637,8 +637,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
637
637
  "add_index :ads, [:id], unique: true, name: 'PRIMARY'\n"
638
638
  elsif defined?(Mysql2)
639
639
  "execute \"ALTER TABLE ads DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
640
- "add_foreign_key(\"adverts\", \"categories\", column: \"category_id\", name: \"index_adverts_on_category_id\")\n" +
641
- "add_foreign_key(\"adverts\", \"categories\", column: \"c_id\", name: \"index_adverts_on_c_id\")"
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
642
  end}
643
643
  EOS
644
644
  .and migrate_down(<<~EOS.strip)
@@ -651,8 +651,8 @@ RSpec.describe 'DeclareSchema Migration Generator' do
651
651
  "add_index :adverts, [:id], unique: true, name: 'PRIMARY'\n"
652
652
  elsif defined?(Mysql2)
653
653
  "execute \"ALTER TABLE adverts DROP PRIMARY KEY, ADD PRIMARY KEY (id)\"\n\n" +
654
- "remove_foreign_key(\"adverts\", name: \"index_adverts_on_category_id\")\n" +
655
- "remove_foreign_key(\"adverts\", name: \"index_adverts_on_c_id\")"
654
+ "remove_foreign_key(\"adverts\", name: \"on_category_id\")\n" +
655
+ "remove_foreign_key(\"adverts\", name: \"on_c_id\")"
656
656
  end}
657
657
  EOS
658
658
  )
@@ -37,6 +37,11 @@ RSpec.describe DeclareSchema::Model::Column do
37
37
  expect(described_class.native_type?(type)).to be_falsey
38
38
  end
39
39
  end
40
+
41
+ it "is truthy when there's a NullDbAdapter (like for assets:precompile) that doesn't have any native types" do
42
+ allow(described_class).to receive(:native_types).and_return({})
43
+ expect(described_class.native_type?(:integer)).to be_truthy
44
+ end
40
45
  end
41
46
 
42
47
  describe '.native_types' do
@@ -59,26 +64,6 @@ RSpec.describe DeclareSchema::Model::Column do
59
64
  end
60
65
  end
61
66
 
62
- describe '.sql_type' do
63
- it 'returns the sql type for :string' do
64
- expect(described_class.sql_type(:string)).to eq(:string)
65
- end
66
-
67
- it 'returns the sql type for :integer' do
68
- expect(described_class.sql_type(:integer)).to match(:integer)
69
- end
70
-
71
- it 'returns the sql type for :datetime' do
72
- expect(described_class.sql_type(:datetime)).to eq(:datetime)
73
- end
74
-
75
- it 'raises UnknownSqlType' do
76
- expect do
77
- described_class.sql_type(:email)
78
- end.to raise_exception(::DeclareSchema::UnknownSqlTypeError, /:email for type :email/)
79
- end
80
- end
81
-
82
67
  describe '.deserialize_default_value' do
83
68
  require 'rails'
84
69
 
@@ -109,10 +94,11 @@ RSpec.describe DeclareSchema::Model::Column do
109
94
  end
110
95
  end
111
96
  let(:model) { ColumnTestModel }
97
+ let(:type) { :integer }
112
98
  let(:current_table_name) { model.table_name }
113
99
  let(:column) { double("ActiveRecord Column",
114
100
  name: 'count',
115
- type: :integer,
101
+ type: type,
116
102
  limit: nil,
117
103
  precision: nil,
118
104
  scale: nil,
@@ -122,9 +108,9 @@ RSpec.describe DeclareSchema::Model::Column do
122
108
  sql_type_metadata: {}) }
123
109
  subject { described_class.new(model, current_table_name, column) }
124
110
 
125
- describe '#sql_type' do
126
- it 'returns sql type' do
127
- expect(subject.sql_type).to match(/int/)
111
+ describe '#type' do
112
+ it 'returns type' do
113
+ expect(subject.type).to eq(type)
128
114
  end
129
115
  end
130
116
 
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ begin
6
+ require 'mysql2'
7
+ rescue LoadError
8
+ end
9
+
10
+ require_relative '../../../../lib/declare_schema/model/habtm_model_shim'
11
+
12
+ RSpec.describe DeclareSchema::Model::HabtmModelShim do
13
+ let(:join_table) { "parent_1_parent_2" }
14
+ let(:foreign_keys) { ["parent_1_id", "parent_2_id"] }
15
+ let(:foreign_key_classes) { [Parent1, Parent2] }
16
+
17
+ before do
18
+ load File.expand_path('../prepare_testapp.rb', __dir__)
19
+
20
+ class Parent1 < ActiveRecord::Base
21
+ self.table_name = "parent_1s"
22
+ end
23
+
24
+ class Parent2 < ActiveRecord::Base
25
+ self.table_name = "parent_2s"
26
+ end
27
+ end
28
+
29
+ describe 'class methods' do
30
+ describe '.from_reflection' do
31
+ let(:reflection) { double("reflection", join_table: join_table,
32
+ foreign_key: foreign_keys.first,
33
+ association_foreign_key: foreign_keys.last,
34
+ active_record: foreign_key_classes.first,
35
+ class_name: 'Parent1') }
36
+ it 'returns a new object' do
37
+ result = described_class.from_reflection(reflection)
38
+
39
+ expect(result).to be_a(described_class)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'instance methods' do
45
+ let(:connection) { instance_double(ActiveRecord::Base.connection.class, "connection") }
46
+
47
+ subject { described_class.new(join_table, foreign_keys, foreign_key_classes, connection) }
48
+
49
+ describe '#initialize' do
50
+ it 'stores initialization attributes' do
51
+ expect(subject.join_table).to eq(join_table)
52
+ expect(subject.foreign_keys).to eq(foreign_keys)
53
+ expect(subject.foreign_key_classes).to be(foreign_key_classes)
54
+ expect(subject.connection).to be(connection)
55
+ end
56
+ end
57
+
58
+ describe '#table_options' do
59
+ it 'returns empty hash' do
60
+ expect(subject.table_options).to eq({})
61
+ end
62
+ end
63
+
64
+ describe '#table_name' do
65
+ it 'returns join_table' do
66
+ expect(subject.table_name).to eq(join_table)
67
+ end
68
+ end
69
+
70
+ describe '#field_specs' do
71
+ it 'returns 2 field specs' do
72
+ result = subject.field_specs
73
+ expect(result.size).to eq(2), result.inspect
74
+
75
+ expect(result[foreign_keys.first]).to be_a(::DeclareSchema::Model::FieldSpec)
76
+ expect(result[foreign_keys.first].model).to eq(subject)
77
+ expect(result[foreign_keys.first].name.to_s).to eq(foreign_keys.first)
78
+ expect(result[foreign_keys.first].type).to eq(:integer)
79
+ expect(result[foreign_keys.first].position).to eq(0)
80
+
81
+ expect(result[foreign_keys.last]).to be_a(::DeclareSchema::Model::FieldSpec)
82
+ expect(result[foreign_keys.last].model).to eq(subject)
83
+ expect(result[foreign_keys.last].name.to_s).to eq(foreign_keys.last)
84
+ expect(result[foreign_keys.last].type).to eq(:integer)
85
+ expect(result[foreign_keys.last].position).to eq(1)
86
+ end
87
+ end
88
+
89
+ describe '#primary_key' do
90
+ it 'returns false' do
91
+ expect(subject.primary_key).to eq(false)
92
+ end
93
+ end
94
+
95
+ describe '#defined_primary_key' do
96
+ it 'returns false' do
97
+ expect(subject.defined_primary_key).to eq(false)
98
+ end
99
+ end
100
+
101
+ describe '#index_definitions_with_primary_key' do
102
+ it 'returns 2 index definitions' do
103
+ result = subject.index_definitions_with_primary_key
104
+ expect(result.size).to eq(2), result.inspect
105
+
106
+ expect(result.first).to be_a(::DeclareSchema::Model::IndexDefinition)
107
+ expect(result.first.name).to eq('PRIMARY')
108
+ expect(result.first.fields).to eq(['parent_1_id', 'parent_2_id'])
109
+ expect(result.first.unique).to be_truthy
110
+
111
+ expect(result.last).to be_a(::DeclareSchema::Model::IndexDefinition)
112
+ expect(result.last.name).to eq('on_parent_2_id')
113
+ expect(result.last.unique).to be_falsey
114
+ expect(result.last.fields).to eq(['parent_2_id'])
115
+ end
116
+ end
117
+
118
+ describe '#index_definitions' do
119
+ it 'returns index_definitions_with_primary_key' do
120
+ result = subject.index_definitions
121
+ expect(result.size).to eq(2), result.inspect
122
+ end
123
+ end
124
+
125
+ describe 'ignore_indexes' do
126
+ it 'returns empty array' do
127
+ expect(subject.ignore_indexes).to eq([])
128
+ end
129
+ end
130
+
131
+ describe '#constraint_specs' do
132
+ it 'returns 2 foreign keys' do
133
+ result = subject.constraint_specs
134
+ expect(result.size).to eq(2), result.inspect
135
+
136
+ expect(result.first).to be_a(::DeclareSchema::Model::ForeignKeyDefinition)
137
+ expect(result.first.foreign_key).to eq(foreign_keys.first)
138
+ expect(result.first.parent_table_name).to be(Parent1.table_name)
139
+ expect(result.first.on_delete_cascade).to be_truthy
140
+
141
+ expect(result.last).to be_a(::DeclareSchema::Model::ForeignKeyDefinition)
142
+ expect(result.last.foreign_key).to eq(foreign_keys.last)
143
+ expect(result.last.parent_table_name).to be(Parent2.table_name)
144
+ expect(result.last.on_delete_cascade).to be_truthy
145
+ end
146
+ end
147
+ end
148
+ end
@@ -58,7 +58,17 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
58
58
  end
59
59
  end
60
60
 
61
- describe 'class << self' do
61
+ describe 'class methods' do
62
+ describe 'index_name' do
63
+ it 'works with a single column' do
64
+ expect(described_class.index_name('parent_id')).to eq('on_parent_id')
65
+ end
66
+
67
+ it 'works with many columns' do
68
+ expect(described_class.index_name(['a', 'b', 'c'])).to eq('on_a_and_b_and_c')
69
+ end
70
+ end
71
+
62
72
  context 'with a migrated database' do
63
73
  before do
64
74
  ActiveRecord::Base.connection.execute <<~EOS
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.7.0
4
+ version: 0.8.0.pre.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca Development adapted from hobo_fields by Tom Locke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-14 00:00:00.000000000 Z
11
+ date: 2021-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -64,6 +64,7 @@ files:
64
64
  - lib/declare_schema/model/column.rb
65
65
  - lib/declare_schema/model/field_spec.rb
66
66
  - lib/declare_schema/model/foreign_key_definition.rb
67
+ - lib/declare_schema/model/habtm_model_shim.rb
67
68
  - lib/declare_schema/model/index_definition.rb
68
69
  - lib/declare_schema/model/table_options_definition.rb
69
70
  - lib/declare_schema/railtie.rb
@@ -85,6 +86,7 @@ files:
85
86
  - spec/lib/declare_schema/migration_generator_spec.rb
86
87
  - spec/lib/declare_schema/model/column_spec.rb
87
88
  - spec/lib/declare_schema/model/foreign_key_definition_spec.rb
89
+ - spec/lib/declare_schema/model/habtm_model_shim_spec.rb
88
90
  - spec/lib/declare_schema/model/index_definition_spec.rb
89
91
  - spec/lib/declare_schema/model/table_options_definition_spec.rb
90
92
  - spec/lib/declare_schema/prepare_testapp.rb