declare_schema 0.7.0 → 0.8.0.pre.4

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: 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