declare_schema 0.10.0 → 0.12.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/Gemfile.lock +1 -1
  4. data/README.md +16 -6
  5. data/lib/declare_schema.rb +12 -1
  6. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +4 -2
  7. data/lib/declare_schema/model.rb +59 -15
  8. data/lib/declare_schema/model/column.rb +2 -2
  9. data/lib/declare_schema/model/field_spec.rb +4 -4
  10. data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
  11. data/lib/declare_schema/model/habtm_model_shim.rb +2 -2
  12. data/lib/declare_schema/model/index_definition.rb +8 -25
  13. data/lib/declare_schema/schema_change/all.rb +22 -0
  14. data/lib/declare_schema/schema_change/base.rb +45 -0
  15. data/lib/declare_schema/schema_change/column_add.rb +27 -0
  16. data/lib/declare_schema/schema_change/column_change.rb +32 -0
  17. data/lib/declare_schema/schema_change/column_remove.rb +20 -0
  18. data/lib/declare_schema/schema_change/column_rename.rb +23 -0
  19. data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
  20. data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
  21. data/lib/declare_schema/schema_change/index_add.rb +33 -0
  22. data/lib/declare_schema/schema_change/index_remove.rb +20 -0
  23. data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
  24. data/lib/declare_schema/schema_change/table_add.rb +37 -0
  25. data/lib/declare_schema/schema_change/table_change.rb +36 -0
  26. data/lib/declare_schema/schema_change/table_remove.rb +22 -0
  27. data/lib/declare_schema/schema_change/table_rename.rb +22 -0
  28. data/lib/declare_schema/version.rb +1 -1
  29. data/lib/generators/declare_schema/migration/USAGE +14 -24
  30. data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
  31. data/lib/generators/declare_schema/migration/migrator.rb +190 -187
  32. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
  33. data/spec/lib/declare_schema/api_spec.rb +3 -1
  34. data/spec/lib/declare_schema/field_spec_spec.rb +3 -3
  35. data/spec/lib/declare_schema/generator_spec.rb +2 -2
  36. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +60 -25
  37. data/spec/lib/declare_schema/migration_generator_spec.rb +471 -377
  38. data/spec/lib/declare_schema/model/column_spec.rb +2 -6
  39. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -16
  40. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -6
  41. data/spec/lib/declare_schema/model/index_definition_spec.rb +4 -4
  42. data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
  43. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
  44. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
  45. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
  46. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
  47. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
  48. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
  49. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
  50. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
  51. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
  52. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
  53. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
  54. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
  55. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
  56. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +59 -11
  57. data/spec/spec_helper.rb +1 -1
  58. data/spec/support/acceptance_spec_helpers.rb +2 -2
  59. metadata +34 -5
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  begin
6
4
  require 'mysql2'
7
5
  rescue LoadError
@@ -16,7 +14,7 @@ RSpec.describe DeclareSchema::Model::Column do
16
14
 
17
15
  describe 'class methods' do
18
16
  describe '.native_type?' do
19
- if Rails::VERSION::MAJOR >= 5
17
+ if ActiveSupport::VERSION::MAJOR >= 5
20
18
  let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :json] }
21
19
  else
22
20
  let(:native_types) { [:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean] }
@@ -65,9 +63,7 @@ RSpec.describe DeclareSchema::Model::Column do
65
63
  end
66
64
 
67
65
  describe '.deserialize_default_value' do
68
- require 'rails'
69
-
70
- if ::Rails::VERSION::MAJOR >= 5
66
+ if ::ActiveSupport::VERSION::MAJOR >= 5
71
67
  it 'deserializes :boolean' do
72
68
  expect(described_class.deserialize_default_value(nil, :boolean, 'true')).to eq(true)
73
69
  expect(described_class.deserialize_default_value(nil, :boolean, 'false')).to eq(false)
@@ -26,7 +26,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
26
26
  subject { described_class.new(model, foreign_key, options)}
27
27
 
28
28
  before do
29
- allow(connection).to receive(:index_name).with('models', column: 'network_id') { 'on_network_id' }
29
+ allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
30
30
  end
31
31
 
32
32
  describe '#initialize' do
@@ -36,7 +36,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
36
36
  end
37
37
 
38
38
  context 'when most options passed' do
39
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id } }
39
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id } }
40
40
 
41
41
  it 'normalizes symbols to strings' do
42
42
  expect(subject.foreign_key).to eq('network_id')
@@ -50,8 +50,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
50
50
 
51
51
  context 'when all options passed' do
52
52
  let(:foreign_key) { nil }
53
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id,
54
- constraint_name: :constraint_1, dependent: :delete } }
53
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
55
54
 
56
55
  it 'normalizes symbols to strings' do
57
56
  expect(subject.foreign_key).to be_nil
@@ -61,11 +60,18 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
61
60
  expect(subject.on_delete_cascade).to be_truthy
62
61
  end
63
62
  end
64
- end
65
63
 
66
- describe '#to_add_statement' do
67
- it 'returns add_foreign_key command' do
68
- expect(subject.to_add_statement).to eq('add_foreign_key("models", "networks", column: "network_id", name: "on_network_id")')
64
+ context 'when constraint name passed as empty string' do
65
+ let(:options) { { constraint_name: "" } }
66
+ it 'defaults to rails constraint name' do
67
+ expect(subject.constraint_name).to eq("index_on_network_id")
68
+ end
69
+ end
70
+
71
+ context 'when no constraint name passed' do
72
+ it 'defaults to rails constraint name' do
73
+ expect(subject.constraint_name).to eq("index_on_network_id")
74
+ end
69
75
  end
70
76
  end
71
77
  end
@@ -113,7 +119,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
113
119
  subject { described_class.new(model, foreign_key, options)}
114
120
 
115
121
  before do
116
- allow(connection).to receive(:index_name).with('models', column: 'network_id') { 'on_network_id' }
122
+ allow(model.connection).to receive(:index_name).with(any_args) { 'index_on_network_id' }
117
123
  end
118
124
 
119
125
  describe '#initialize' do
@@ -123,7 +129,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
123
129
  end
124
130
 
125
131
  context 'when most options passed' do
126
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id } }
132
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id } }
127
133
 
128
134
  it 'normalizes symbols to strings' do
129
135
  expect(subject.foreign_key).to eq('network_id')
@@ -137,8 +143,7 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
137
143
 
138
144
  context 'when all options passed' do
139
145
  let(:foreign_key) { nil }
140
- let(:options) { { parent_table: :networks, foreign_key: :the_network_id, index_name: :index_on_network_id,
141
- constraint_name: :constraint_1, dependent: :delete } }
146
+ let(:options) { { parent_table: :networks, foreign_key: :the_network_id, constraint_name: :constraint_1, dependent: :delete } }
142
147
 
143
148
  it 'normalizes symbols to strings' do
144
149
  expect(subject.foreign_key).to be_nil
@@ -148,11 +153,18 @@ RSpec.describe DeclareSchema::Model::ForeignKeyDefinition do
148
153
  expect(subject.on_delete_cascade).to be_truthy
149
154
  end
150
155
  end
151
- end
152
156
 
153
- describe '#to_add_statement' do
154
- it 'returns add_foreign_key command' do
155
- expect(subject.to_add_statement).to eq('add_foreign_key("models", "networks", column: "network_id", name: "on_network_id")')
157
+ context 'when constraint name passed as empty string' do
158
+ let(:options) { { constraint_name: "" } }
159
+ it 'defaults to rails constraint name' do
160
+ expect(subject.constraint_name).to eq("index_on_network_id")
161
+ end
162
+ end
163
+
164
+ context 'when no constraint name passed' do
165
+ it 'defaults to rails constraint name' do
166
+ expect(subject.constraint_name).to eq("index_on_network_id")
167
+ end
156
168
  end
157
169
  end
158
170
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails'
4
-
5
3
  begin
6
4
  require 'mysql2'
7
5
  rescue LoadError
@@ -57,7 +55,7 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
57
55
 
58
56
  describe '#table_options' do
59
57
  it 'returns empty hash' do
60
- expect(subject.table_options).to eq({})
58
+ expect(subject._table_options).to eq({})
61
59
  end
62
60
  end
63
61
 
@@ -88,13 +86,13 @@ RSpec.describe DeclareSchema::Model::HabtmModelShim do
88
86
 
89
87
  describe '#primary_key' do
90
88
  it 'returns false' do
91
- expect(subject._defined_primary_key).to eq(false)
89
+ expect(subject._declared_primary_key).to eq(false)
92
90
  end
93
91
  end
94
92
 
95
- describe '#_defined_primary_key' do
93
+ describe '#_declared_primary_key' do
96
94
  it 'returns false' do
97
- expect(subject._defined_primary_key).to eq(false)
95
+ expect(subject._declared_primary_key).to eq(false)
98
96
  end
99
97
  end
100
98
 
@@ -75,7 +75,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
75
75
  ActiveRecord::Base.connection.execute <<~EOS
76
76
  CREATE TABLE index_definition_test_models (
77
77
  id INTEGER NOT NULL PRIMARY KEY,
78
- name #{if defined?(Sqlite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
78
+ name #{if defined?(SQLite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
79
79
  )
80
80
  EOS
81
81
  ActiveRecord::Base.connection.execute <<~EOS
@@ -110,7 +110,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
110
110
  let(:model_class) { IndexDefinitionCompoundIndexModel }
111
111
 
112
112
  it 'returns the indexes for the model' do
113
- if Rails::VERSION::MAJOR < 5
113
+ if ActiveSupport::VERSION::MAJOR < 5
114
114
  expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
115
115
  connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
116
116
  expect(connection_stub).to receive(:indexes).
@@ -184,7 +184,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
184
184
  ActiveRecord::Base.connection.execute <<~EOS
185
185
  CREATE TABLE index_definition_test_models (
186
186
  id INTEGER NOT NULL PRIMARY KEY,
187
- name #{if defined?(Sqlite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
187
+ name #{if defined?(SQLite3) then 'TEXT' else 'VARCHAR(255)' end} NOT NULL
188
188
  )
189
189
  EOS
190
190
  ActiveRecord::Base.connection.execute <<~EOS
@@ -219,7 +219,7 @@ RSpec.describe DeclareSchema::Model::IndexDefinition do
219
219
  let(:model_class) { IndexDefinitionCompoundIndexModel }
220
220
 
221
221
  it 'returns the indexes for the model' do
222
- if Rails::VERSION::MAJOR < 5
222
+ if ActiveSupport::VERSION::MAJOR < 5
223
223
  expect(model_class.connection).to receive(:primary_key).with('index_definition_compound_index_models').and_return(nil)
224
224
  connection_stub = instance_double(ActiveRecord::Base.connection.class, "connection")
225
225
  expect(connection_stub).to receive(:indexes).
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/base'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::Base do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ before :all do
11
+ class ChangeDefault < described_class
12
+ attr_reader :up_command, :down_command
13
+
14
+ def initialize(up:, down:)
15
+ @up_command = up
16
+ @down_command = down
17
+ end
18
+ end
19
+
20
+ class ChangeOverride < described_class
21
+ attr_reader :up_command, :down_command
22
+
23
+ def initialize(up:, down:)
24
+ @up_command = up
25
+ @down_command = down
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'class methods' do
31
+ describe 'format_options' do
32
+ subject { { limit: 8, 'key' => 'value', subhash: { subkey: :subvalue } } }
33
+
34
+ it 'formats using Ruby 2.0 symbol notation' do
35
+ expect(described_class.format_options(subject)).to eq(['limit: 8', '"key" => "value"', 'subhash: { subkey: :subvalue }'])
36
+ end
37
+ end
38
+ end
39
+
40
+ describe 'instance methods' do
41
+ describe '#up/#down' do
42
+ context 'with single-line commands' do
43
+ subject { ChangeDefault.new(up: "up_command", down: "down_command" )}
44
+
45
+ describe '#up' do
46
+ it 'responds with command and single spacing' do
47
+ expect(subject.up).to eq("up_command\n")
48
+ end
49
+ end
50
+
51
+ describe '#down' do
52
+ it 'responds with command and single spacing' do
53
+ expect(subject.down).to eq("down_command\n")
54
+ end
55
+ end
56
+ end
57
+
58
+ context 'with multi-line commands' do
59
+ subject { ChangeDefault.new(up: "up_command 1\nup_command 2", down: "down_command 1\ndown_command 2" )}
60
+
61
+ describe '#up' do
62
+ it 'responds with command and double spacing' do
63
+ expect(subject.up).to eq("up_command 1\nup_command 2\n\n")
64
+ end
65
+ end
66
+
67
+ describe '#down' do
68
+ it 'responds with command and spacing' do
69
+ expect(subject.down).to eq("down_command 1\ndown_command 2\n\n")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/column_add'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ColumnAdd do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:column_name) { 'title' }
12
+ let(:column_type) { :integer }
13
+ let(:column_options) { { limit: 8 } }
14
+ let(:column_options_string) { "limit: 8" }
15
+ subject { described_class.new(table_name, column_name, column_type, column_options) }
16
+
17
+ describe '#up/down' do
18
+ describe '#up' do
19
+ it 'responds with command' do
20
+ expect(subject.up).to eq("add_column :#{table_name}, :#{column_name}, :#{column_type}, #{column_options_string}\n")
21
+ end
22
+ end
23
+
24
+ describe '#down' do
25
+ it 'responds with command' do
26
+ expect(subject.down).to eq("remove_column :#{table_name}, :#{column_name}\n")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/column_change'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ColumnChange do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:column_name) { 'title' }
12
+ let(:old_type) { :string }
13
+ let(:old_options) { { limit: 255, null: false } }
14
+ let(:old_options_string) { "limit: 255, null: false" }
15
+ let(:new_type) { :text }
16
+ let(:new_options) { { limit: 0xffff, null: true } }
17
+ let(:new_options_string) { "limit: 65535, null: true" }
18
+ subject { described_class.new(table_name, column_name, old_type: old_type, old_options: old_options, new_type: new_type, new_options: new_options) }
19
+
20
+ describe '#up/down' do
21
+ describe '#up' do
22
+ it 'responds with command' do
23
+ expect(subject.up).to eq("change_column :#{table_name}, :#{column_name}, :#{new_type}, #{new_options_string}\n")
24
+ end
25
+ end
26
+
27
+ describe '#down' do
28
+ it 'responds with command' do
29
+ expect(subject.down).to eq("change_column :#{table_name}, :#{column_name}, :#{old_type}, #{old_options_string}\n")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/column_remove'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ColumnRemove do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:column_name) { 'title' }
12
+ let(:column_type) { :integer }
13
+ let(:column_options) { { limit: 8 } }
14
+ let(:column_options_string) { "limit: 8" }
15
+ subject { described_class.new(table_name, column_name, column_type, column_options) }
16
+
17
+ describe '#up/down' do
18
+ describe '#up' do
19
+ it 'responds with command' do
20
+ expect(subject.up).to eq("remove_column :#{table_name}, :#{column_name}\n")
21
+ end
22
+ end
23
+
24
+ describe '#down' do
25
+ it 'responds with command' do
26
+ expect(subject.down).to eq("add_column :#{table_name}, :#{column_name}, :#{column_type}, #{column_options_string}\n")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/column_rename'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ColumnRename do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'networks' }
11
+ let(:old_name) { 'title' }
12
+ let(:new_name) { 'summary' }
13
+ subject { described_class.new(table_name, old_name, new_name) }
14
+
15
+ describe '#up/down' do
16
+ describe '#up' do
17
+ it 'responds with command' do
18
+ expect(subject.up).to eq("rename_column :#{table_name}, :#{old_name}, :#{new_name}\n")
19
+ end
20
+ end
21
+
22
+ describe '#down' do
23
+ it 'responds with command' do
24
+ expect(subject.down).to eq("rename_column :#{table_name}, :#{new_name}, :#{old_name}\n")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/foreign_key_add'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ForeignKeyAdd do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'users' }
11
+ let(:parent_table_name) { 'organization' }
12
+ let(:column_name) { :organization_id }
13
+ let(:name) { 'on_organization_id' }
14
+ subject { described_class.new(table_name, parent_table_name, column_name: column_name, name: name) }
15
+
16
+ describe '#up/down' do
17
+ describe '#up' do
18
+ it 'responds with command' do
19
+ expect(subject.up).to eq("add_foreign_key :#{table_name}, :#{parent_table_name}, column: #{column_name.to_sym.inspect}, name: #{name.to_sym.inspect}\n")
20
+ end
21
+ end
22
+
23
+ describe '#down' do
24
+ it 'responds with command' do
25
+ expect(subject.down).to eq("remove_foreign_key :#{table_name}, name: #{name.to_sym.inspect}\n")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../lib/declare_schema/schema_change/foreign_key_remove'
4
+
5
+ RSpec.describe DeclareSchema::SchemaChange::ForeignKeyRemove do
6
+ before do
7
+ load File.expand_path('../prepare_testapp.rb', __dir__)
8
+ end
9
+
10
+ let(:table_name) { 'users' }
11
+ let(:parent_table_name) { 'organization' }
12
+ let(:column_name) { :organization_id }
13
+ let(:name) { 'on_organization_id' }
14
+ subject { described_class.new(table_name, parent_table_name, column_name: column_name, name: name) }
15
+
16
+ describe '#up/down' do
17
+ describe '#up' do
18
+ it 'responds with command' do
19
+ expect(subject.up).to eq("remove_foreign_key :#{table_name}, name: #{name.to_sym.inspect}\n")
20
+ end
21
+ end
22
+
23
+ describe '#down' do
24
+ it 'responds with command' do
25
+ expect(subject.down).to eq("add_foreign_key :#{table_name}, :#{parent_table_name}, column: #{column_name.to_sym.inspect}, name: #{name.to_sym.inspect}\n")
26
+ end
27
+ end
28
+ end
29
+ end