declare_schema 0.3.0 → 0.5.0.pre.1

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: f3ae0814480b116b32a822887c83612b33458cc910384ae0c2122e547757f141
4
- data.tar.gz: 133b7bd6d7d91dae9320a4e8002a8591e43ebb97f57013cbe266cef40a169088
3
+ metadata.gz: 3f3b9aed36bcc541fb7d929a97e3650358be4da05c33a5d27e8db732633eb8f8
4
+ data.tar.gz: 800c945108be2e29301afc2badce461a1302dadad7c393bd39c939368311e874
5
5
  SHA512:
6
- metadata.gz: 77b840eb5feb07d38c4fd96f4212d4c81f19c61a2aa2b4bbffb4f4f11594ba55c30e65ca4b77a68e7b29448f7762dba52782f1845307c5bbc02f7603f080c2e8
7
- data.tar.gz: 2f85affbd44a6973595883afa01766ede6c950a70c4e783f3064e5c2a59ec5c71a3731d7d484bab133c18c698584077e2c14a8b98c81ce024e2e55bae59dabba
6
+ metadata.gz: 821e56fe2ba4e7c9913f630a47cf685a565c19d40fdf282305c3bf0db6f0783b3174aa4e1ca535732e64cc7514cd87a598eaf743fbca0b21fa7041ec62ce82a0
7
+ data.tar.gz: c2522286a389e559fda6b635b0d85b1025bdf918a98a5eb27e40946d5eee1e079efca53a0e2ad6f24649a8b05f59d652869cee924dcca7491ff7575d8292519c
@@ -0,0 +1,14 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ day: friday
8
+ time: "22:00"
9
+ timezone: PST8PDT
10
+ open-pull-requests-limit: 99
11
+ versioning-strategy: lockfile-only
12
+ commit-message:
13
+ prefix: No-Jira
14
+ include: scope
@@ -4,6 +4,37 @@ 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.5.0] - Unreleased
8
+ ### Added
9
+ - Added support for configuring the character set and collation for MySQL databases
10
+ at the global, table, and field level
11
+
12
+ ## [0.4.2] - 2020-12-05
13
+ ### Fixed
14
+ - Generalize the fix below to sqlite || Rails 4.
15
+
16
+ ## [0.4.1] - 2020-12-04
17
+ ### Fixed
18
+ - Fixed a bug detecting compound primary keys in Rails 4.
19
+
20
+ ## [0.4.0] - 2020-11-20
21
+ ### Added
22
+ - Fields may be declared with `serialize: true` (any value with a valid `.to_yaml` stored as YAML),
23
+ or `serialize: <serializeable-class>`, where `<serializeable-class>`
24
+ may be `Array` (`Array` stored as YAML) or `Hash` (`Hash` stored as YAML) or `JSON` (any value with a valid `.to_json`, stored as JSON)
25
+ or any custom serializable class.
26
+ This invokes `ActiveSupport`'s `serialize` macro for that field, passing the serializable class, if given.
27
+
28
+ Note: when `serialize:` is used, any `default:` should be given in a matching Ruby type--for example, `[]` or `{}` or `{ 'currency' => 'USD' }`--in
29
+ which case the serializeable class will be used to determine the serialized default value and that will be set as the SQL default.
30
+
31
+ ### Fixed
32
+ - Sqlite now correctly infers the PRIMARY KEY so it won't attempt to add that index again.
33
+
34
+ ## [0.3.1] - 2020-11-13
35
+ ### Fixed
36
+ - When passing `belongs_to` to Rails, suppress the `optional:` option in Rails 4, since that option was added in Rails 5.
37
+
7
38
  ## [0.3.0] - 2020-11-02
8
39
  ### Added
9
40
  - Added support for `belongs_to optional:`.
@@ -38,11 +69,15 @@ using the appropriate Rails configuration attributes.
38
69
  ### Changed
39
70
  - Added travis support and created 2 specs as a starting point.
40
71
 
41
-
42
72
  ## [0.1.1] - 2020-09-24
43
73
  ### Added
44
74
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
45
75
 
76
+ [0.5.0]: https://github.com/Invoca/declare_schema/compare/v0.4.2...v0.5.0
77
+ [0.4.2]: https://github.com/Invoca/declare_schema/compare/v0.4.1...v0.4.2
78
+ [0.4.1]: https://github.com/Invoca/declare_schema/compare/v0.4.0...v0.4.1
79
+ [0.4.0]: https://github.com/Invoca/declare_schema/compare/v0.3.1...v0.4.0
80
+ [0.3.1]: https://github.com/Invoca/declare_schema/compare/v0.3.0...v0.3.1
46
81
  [0.3.0]: https://github.com/Invoca/declare_schema/compare/v0.2.0...v0.3.0
47
82
  [0.2.0]: https://github.com/Invoca/declare_schema/compare/v0.1.3...v0.2.0
48
83
  [0.1.3]: https://github.com/Invoca/declare_schema/compare/v0.1.2...v0.1.3
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ gem 'bundler', '< 2'
14
14
  gem "climate_control", '~> 0.2'
15
15
  gem 'pry'
16
16
  gem 'pry-byebug'
17
+ gem 'mysql2'
17
18
  gem 'rails', '~> 5.2', '>= 5.2.4.3'
18
19
  gem 'responders'
19
20
  gem 'rspec'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.3.0)
4
+ declare_schema (0.5.0.pre.1)
5
5
  rails (>= 4.2)
6
6
 
7
7
  GEM
@@ -54,7 +54,7 @@ GEM
54
54
  thor (>= 0.14.0)
55
55
  arel (9.0.0)
56
56
  ast (2.4.1)
57
- bootsnap (1.4.8)
57
+ bootsnap (1.5.1)
58
58
  msgpack (~> 1.0)
59
59
  builder (3.2.4)
60
60
  byebug (11.1.3)
@@ -69,7 +69,7 @@ GEM
69
69
  activesupport (>= 4.2.0)
70
70
  i18n (1.8.5)
71
71
  concurrent-ruby (~> 1.0)
72
- listen (3.2.1)
72
+ listen (3.3.1)
73
73
  rb-fsevent (~> 0.10, >= 0.10.3)
74
74
  rb-inotify (~> 0.9, >= 0.9.10)
75
75
  loofah (2.7.0)
@@ -85,6 +85,7 @@ GEM
85
85
  mini_portile2 (2.4.0)
86
86
  minitest (5.14.2)
87
87
  msgpack (1.3.3)
88
+ mysql2 (0.5.3)
88
89
  nio4r (2.5.4)
89
90
  nokogiri (1.10.10)
90
91
  mini_portile2 (~> 2.4.0)
@@ -134,19 +135,19 @@ GEM
134
135
  actionpack (>= 5.0)
135
136
  railties (>= 5.0)
136
137
  rexml (3.2.4)
137
- rspec (3.9.0)
138
- rspec-core (~> 3.9.0)
139
- rspec-expectations (~> 3.9.0)
140
- rspec-mocks (~> 3.9.0)
141
- rspec-core (3.9.2)
142
- rspec-support (~> 3.9.3)
143
- rspec-expectations (3.9.2)
138
+ rspec (3.10.0)
139
+ rspec-core (~> 3.10.0)
140
+ rspec-expectations (~> 3.10.0)
141
+ rspec-mocks (~> 3.10.0)
142
+ rspec-core (3.10.0)
143
+ rspec-support (~> 3.10.0)
144
+ rspec-expectations (3.10.0)
144
145
  diff-lcs (>= 1.2.0, < 2.0)
145
- rspec-support (~> 3.9.0)
146
- rspec-mocks (3.9.1)
146
+ rspec-support (~> 3.10.0)
147
+ rspec-mocks (3.10.0)
147
148
  diff-lcs (>= 1.2.0, < 2.0)
148
- rspec-support (~> 3.9.0)
149
- rspec-support (3.9.3)
149
+ rspec-support (~> 3.10.0)
150
+ rspec-support (3.10.0)
150
151
  rubocop (0.91.0)
151
152
  parallel (~> 1.10)
152
153
  parser (>= 2.7.1.1)
@@ -187,6 +188,7 @@ DEPENDENCIES
187
188
  climate_control (~> 0.2)
188
189
  declare_schema!
189
190
  listen
191
+ mysql2
190
192
  pry
191
193
  pry-byebug
192
194
  rails (~> 5.2, >= 5.2.4.3)
data/README.md CHANGED
@@ -70,6 +70,72 @@ DeclareSchema::Migration::Migrator.before_generating_migration do
70
70
  end
71
71
  ```
72
72
 
73
+ ## Declaring Character Set and Collation
74
+ _Note: This feature currently only works for MySQL database configurations._
75
+
76
+ MySQL originally supported UTF-8 in the range of 1-3 bytes (`mb3` or "multi-byte 3")
77
+ which covered the full set of Unicode code points at the time: U+0000 - U+FFFF.
78
+ But later, Unicode was extended beyond U+FFFF to make room for emojis, and with that
79
+ UTF-8 require 1-4 bytes (`mb4` or "multi-byte 4"). With this addition, there has
80
+ come a need to dynamically define the character set and collation for individual
81
+ tables and columns in the database. With `declare_schema` this can be configured
82
+ at three separate levels
83
+
84
+ ### Global Configuration
85
+ The character set and collation for all tables and fields can be set at the global level
86
+ using the `Generators::DeclareSchema::Migrator.default_charset=` and
87
+ `Generators::DeclareSchema::Migrator.default_collation=` configuration methods.
88
+
89
+ For example, adding the following to your `config/initializers` directory will
90
+ turn all tables into `utf8mb4` supporting tables:
91
+
92
+ **declare_schema.rb**
93
+ ```ruby
94
+ # frozen_string_literal: true
95
+
96
+ Generators::DeclareSchema::Migrator.default_charset = "utf8mb4"
97
+ Generators::DeclareSchema::Migrator.default_collation = "utf8mb4_general"
98
+ ```
99
+
100
+ ### Table Configuration
101
+ In order to configure a table's default character set and collation, the `charset` and
102
+ `collation` arguments can be added to the `fields` block.
103
+
104
+ For example, if you have a comments model that needs `utf8mb4` support, it would look
105
+ like the following:
106
+
107
+ **app/models/comment.rb**
108
+ ```ruby
109
+ # frozen_string_literal: true
110
+
111
+ class Comment < ActiveRecord::Base
112
+ fields charset: "utf8mb4", collation: "utf8mb4_general" do
113
+ subject :string, limit: 255
114
+ content :text, limit: 0xffff_ffff
115
+ end
116
+ end
117
+ ```
118
+
119
+ ### Field Configuration
120
+ If you're looking to only change the character set and collation for a single field
121
+ in the table, simply set the `charset` and `collation` configuration options on the
122
+ field definition itself.
123
+
124
+ For example, if you only want to support `utf8mb4` for the content of a comment, it would
125
+ look like the following:
126
+
127
+ **app/models/comment.rb**
128
+ ```ruby
129
+ # frozen_string_literal: true
130
+
131
+ class Comment < ActiveRecord::Base
132
+ fields do
133
+ subject :string, limit: 255
134
+ context :text, limit: 0xffff_ffff, charset: "utf8mb4", collation: "utf8mb4_general"
135
+ end
136
+ end
137
+ ```
138
+
73
139
  ## Installing
74
140
 
75
141
  Install the `DeclareSchema` gem directly:
@@ -7,6 +7,7 @@ gem "bundler", "< 2"
7
7
  gem "climate_control", "~> 0.2"
8
8
  gem "pry"
9
9
  gem "pry-byebug"
10
+ gem "mysql2"
10
11
  gem "rails", "~> 4.2"
11
12
  gem "responders"
12
13
  gem "rspec"
@@ -7,6 +7,7 @@ gem "bundler", "< 2"
7
7
  gem "climate_control", "~> 0.2"
8
8
  gem "pry"
9
9
  gem "pry-byebug"
10
+ gem "mysql2"
10
11
  gem "rails", "~> 5.2"
11
12
  gem "responders"
12
13
  gem "rspec"
@@ -7,6 +7,7 @@ gem "bundler", "< 2"
7
7
  gem "climate_control", "~> 0.2"
8
8
  gem "pry"
9
9
  gem "pry-byebug"
10
+ gem "mysql2"
10
11
  gem "rails", "~> 6.0"
11
12
  gem "responders"
12
13
  gem "rspec"
@@ -39,6 +39,8 @@ require 'declare_schema/extensions/active_record/fields_declaration'
39
39
  require 'declare_schema/field_declaration_dsl'
40
40
  require 'declare_schema/model'
41
41
  require 'declare_schema/model/field_spec'
42
- require 'declare_schema/model/index_spec'
42
+ require 'declare_schema/model/index_definition'
43
+ require 'declare_schema/model/foreign_key_definition'
44
+ require 'declare_schema/model/table_options_definition'
43
45
 
44
46
  require 'declare_schema/railtie' if defined?(Rails)
@@ -6,12 +6,13 @@ require 'declare_schema/field_declaration_dsl'
6
6
 
7
7
  module DeclareSchema
8
8
  module FieldsDsl
9
- def fields(&block)
9
+ def fields(table_options = {}, &block)
10
10
  # Any model that calls 'fields' gets DeclareSchema::Model behavior
11
11
  DeclareSchema::Model.mix_in(self)
12
12
 
13
13
  # @include_in_migration = false #||= options.fetch(:include_in_migration, true); options.delete(:include_in_migration)
14
14
  @include_in_migration = true
15
+ @table_options = table_options
15
16
 
16
17
  if block
17
18
  dsl = DeclareSchema::FieldDeclarationDsl.new(self, null: false)
@@ -25,11 +25,15 @@ module DeclareSchema
25
25
  # and speeds things up a little.
26
26
  inheriting_cattr_reader field_specs: HashWithIndifferentAccess.new
27
27
 
28
- # index_specs holds IndexSpec objects for all the declared indexes.
29
- inheriting_cattr_reader index_specs: []
28
+ # index_definitions holds IndexDefinition objects for all the declared indexes.
29
+ inheriting_cattr_reader index_definitions: []
30
30
  inheriting_cattr_reader ignore_indexes: []
31
31
  inheriting_cattr_reader constraint_specs: []
32
32
 
33
+ # table_options holds optional configuration for the create_table statement
34
+ # supported options include :charset and :collation
35
+ inheriting_cattr_reader table_options: HashWithIndifferentAccess.new
36
+
33
37
  # eval avoids the ruby 1.9.2 "super from singleton method ..." error
34
38
 
35
39
  eval %(
@@ -51,19 +55,19 @@ module DeclareSchema
51
55
  def index(fields, options = {})
52
56
  # don't double-index fields
53
57
  index_fields_s = Array.wrap(fields).map(&:to_s)
54
- unless index_specs.any? { |index_spec| index_spec.fields == index_fields_s }
55
- index_specs << ::DeclareSchema::Model::IndexSpec.new(self, fields, options)
58
+ unless index_definitions.any? { |index_spec| index_spec.fields == index_fields_s }
59
+ index_definitions << ::DeclareSchema::Model::IndexDefinition.new(self, fields, options)
56
60
  end
57
61
  end
58
62
 
59
63
  def primary_key_index(*fields)
60
- index(fields.flatten, unique: true, name: "PRIMARY_KEY")
64
+ index(fields.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
61
65
  end
62
66
 
63
67
  def constraint(fkey, options = {})
64
68
  fkey_s = fkey.to_s
65
69
  unless constraint_specs.any? { |constraint_spec| constraint_spec.foreign_key == fkey_s }
66
- constraint_specs << DeclareSchema::Model::ForeignKeySpec.new(self, fkey, options)
70
+ constraint_specs << DeclareSchema::Model::ForeignKeyDefinition.new(self, fkey, options)
67
71
  end
68
72
  end
69
73
 
@@ -79,19 +83,20 @@ module DeclareSchema
79
83
  # declarations.
80
84
  def declare_field(name, type, *args)
81
85
  options = args.extract_options!
82
- field_added(name, type, args, options) if respond_to?(:field_added)
83
- add_formatting_for_field(name, type, args)
86
+ try(:field_added, name, type, args, options)
87
+ add_serialize_for_field(name, type, options)
88
+ add_formatting_for_field(name, type)
84
89
  add_validations_for_field(name, type, args, options)
85
90
  add_index_for_field(name, args, options)
86
91
  field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, options)
87
- attr_order << name unless name.in?(attr_order)
92
+ attr_order << name unless attr_order.include?(name)
88
93
  end
89
94
 
90
- def index_specs_with_primary_key
91
- if index_specs.any?(&:primary_key?)
92
- index_specs
95
+ def index_definitions_with_primary_key
96
+ if index_definitions.any?(&:primary_key?)
97
+ index_definitions
93
98
  else
94
- index_specs + [rails_default_primary_key]
99
+ index_definitions + [rails_default_primary_key]
95
100
  end
96
101
  end
97
102
 
@@ -102,11 +107,11 @@ module DeclareSchema
102
107
  private
103
108
 
104
109
  def rails_default_primary_key
105
- ::DeclareSchema::Model::IndexSpec.new(self, [primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexSpec::PRIMARY_KEY_NAME)
110
+ ::DeclareSchema::Model::IndexDefinition.new(self, [primary_key.to_sym], unique: true, name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
106
111
  end
107
112
 
108
113
  # Extend belongs_to so that it creates a FieldSpec for the foreign key
109
- def belongs_to(name, scope = nil, **options, &block)
114
+ def belongs_to(name, scope = nil, **options)
110
115
  column_options = {}
111
116
 
112
117
  column_options[:null] = if options.has_key?(:null)
@@ -128,13 +133,17 @@ module DeclareSchema
128
133
 
129
134
  fk = options[:foreign_key]&.to_s || "#{name}_id"
130
135
 
131
- if !options.has_key?(:optional) && Rails::VERSION::MAJOR >= 5
136
+ if !options.has_key?(:optional)
132
137
  options[:optional] = column_options[:null] # infer :optional from :null
133
138
  end
134
139
 
135
140
  fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
136
141
 
137
- super(name, scope, options)
142
+ if Rails::VERSION::MAJOR >= 5
143
+ super
144
+ else
145
+ super(name, scope, options.except(:optional))
146
+ end
138
147
 
139
148
  refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
140
149
  fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
@@ -145,7 +154,6 @@ module DeclareSchema
145
154
  index([foreign_type, fkey], index_options) if index_options[:name] != false
146
155
  else
147
156
  index(fkey, index_options) if index_options[:name] != false
148
- options[:constraint_name] = options
149
157
  constraint(fkey, fk_options) if fk_options[:constraint_name] != false
150
158
  end
151
159
  end
@@ -163,9 +171,8 @@ module DeclareSchema
163
171
  # does not effect the attribute in any way - it just records the
164
172
  # metadata.
165
173
  def declare_attr_type(name, type, options = {})
166
- klass = DeclareSchema.to_class(type)
167
- attr_types[name] = DeclareSchema.to_class(type)
168
- klass.declared(self, name, options) if klass.respond_to?(:declared)
174
+ attr_types[name] = klass = DeclareSchema.to_class(type)
175
+ klass.try(:declared, self, name, options)
169
176
  end
170
177
 
171
178
  # Add field validations according to arguments in the
@@ -189,7 +196,37 @@ module DeclareSchema
189
196
  end
190
197
  end
191
198
 
192
- def add_formatting_for_field(name, type, _args)
199
+ def add_serialize_for_field(name, type, options)
200
+ if (serialize_class = options.delete(:serialize))
201
+ type == :string || type == :text or raise ArgumentError, "serialize field type must be :string or :text"
202
+ serialize_args = Array((serialize_class unless serialize_class == true))
203
+ serialize(name, *serialize_args)
204
+ if options.has_key?(:default)
205
+ options[:default] = serialized_default(name, serialize_class == true ? Object : serialize_class, options[:default])
206
+ end
207
+ end
208
+ end
209
+
210
+ def serialized_default(attr_name, class_name_or_coder, default)
211
+ # copied from https://github.com/rails/rails/blob/7d6cb950e7c0e31c2faaed08c81743439156c9f5/activerecord/lib/active_record/attribute_methods/serialization.rb#L70-L76
212
+ coder = if class_name_or_coder == ::JSON
213
+ ActiveRecord::Coders::JSON
214
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
215
+ class_name_or_coder
216
+ elsif Rails::VERSION::MAJOR >= 5
217
+ ActiveRecord::Coders::YAMLColumn.new(attr_name, class_name_or_coder)
218
+ else
219
+ ActiveRecord::Coders::YAMLColumn.new(class_name_or_coder)
220
+ end
221
+
222
+ if default == coder.load(nil)
223
+ nil # handle Array default: [] or Hash default: {}
224
+ else
225
+ coder.dump(default)
226
+ end
227
+ end
228
+
229
+ def add_formatting_for_field(name, type)
193
230
  if (type_class = DeclareSchema.to_class(type))
194
231
  if "format".in?(type_class.instance_methods)
195
232
  before_validation do |record|