declare_schema 0.1.3 → 0.2.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: c5a1596f905dad5d9dd2f08787607c7b9adc742bbb54702bf4cf4aca812e6912
4
- data.tar.gz: 60ec900d6aeb5981d0dd2df7dc689ad01a243e3c1f5b575f33813f5196614c26
3
+ metadata.gz: 8ce85b3e273a189a4ab8b48e333c2f12b2aea4962c0c277ae3c289f1904cddd8
4
+ data.tar.gz: d12345f0a483effa2724e0cd315efa759b2ace23e82ef9a1ae2cdaae28aa2571
5
5
  SHA512:
6
- metadata.gz: b5dc90e36aeb33f00426de19f9df2b85d2bd40754d3bbd6bb8ff8ba410dda808139434d656e305cb9c13320e3cc4130613f06c06ef5abef7e34be4f3ed55e498
7
- data.tar.gz: 39e445fb41afbf2a91dcaa1271040ede91997abcd8e6237616f1e24983d5e88c5aa77c23bcd6c98c9b104f1c8ad68b9456c34a067a807044c2d72893f52e7b3b
6
+ metadata.gz: 84cfbdd55c37a0404b672fbbca5c12dfeee765b0582cdf369b8b3f95159cfb8f82eb3fb1e6149e830ba4fe4481d6df6ef4df45398e30985380ae6681c731f8a9
7
+ data.tar.gz: adeda560e6bb859104b78e3710fb94d438606cf42c10b199527e5d14545505dda6dde3206500a5f7ea38b79530952182954f5e3892779f9d96db9270d59a28f1
@@ -4,6 +4,13 @@ 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.2.0] - Unreleased
8
+ ### Added
9
+ - Automatically eager_load! all Rails::Engines before generating migrations.
10
+
11
+ ### Changed
12
+ - Changed tests from rdoctest to rspec.
13
+
7
14
  ## [0.1.3] - Unreleased
8
15
  ### Changed
9
16
  - Updated the `always_ignore_tables` list in `Migrator` to access Rails metadata table names
@@ -18,6 +25,7 @@ using the appropriate Rails configuration attributes.
18
25
  ### Added
19
26
  - Initial version from https://github.com/Invoca/hobo_fields v4.1.0.
20
27
 
28
+ [0.2.0]: https://github.com/Invoca/declare_schema/compare/v0.1.3...v0.2.0
21
29
  [0.1.3]: https://github.com/Invoca/declare_schema/compare/v0.1.2...v0.1.3
22
30
  [0.1.2]: https://github.com/Invoca/declare_schema/compare/v0.1.1...v0.1.2
23
31
  [0.1.1]: https://github.com/Invoca/declare_schema/tree/v0.1.1
data/Gemfile CHANGED
@@ -18,6 +18,5 @@ gem 'rails', '~> 5.2', '>= 5.2.4.3'
18
18
  gem 'responders'
19
19
  gem 'rspec'
20
20
  gem 'rubocop'
21
- gem 'rubydoctest'
22
21
  gem 'sqlite3'
23
22
  gem 'yard'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- declare_schema (0.1.3)
4
+ declare_schema (0.2.0.pre.1)
5
5
  rails (>= 4.2)
6
6
 
7
7
  GEM
@@ -159,7 +159,6 @@ GEM
159
159
  rubocop-ast (0.4.2)
160
160
  parser (>= 2.7.1.4)
161
161
  ruby-progressbar (1.10.1)
162
- rubydoctest (1.1.5)
163
162
  sprockets (4.0.2)
164
163
  concurrent-ruby (~> 1.0)
165
164
  rack (> 1, < 3)
@@ -194,7 +193,6 @@ DEPENDENCIES
194
193
  responders
195
194
  rspec
196
195
  rubocop
197
- rubydoctest
198
196
  sqlite3
199
197
  yard
200
198
 
data/Rakefile CHANGED
@@ -14,7 +14,6 @@ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
14
14
  require 'declare_schema'
15
15
 
16
16
  RUBY = 'ruby'
17
- RUBYDOCTEST = ENV['RUBYDOCTEST'] || "#{RUBY} -S rubydoctest"
18
17
  GEM_ROOT = __dir__
19
18
  TESTAPP_PATH = ENV['TESTAPP_PATH'] || File.join(Dir.tmpdir, 'declare_schema_testapp')
20
19
  BIN = File.expand_path('bin/declare_schema', __dir__)
@@ -24,13 +23,7 @@ task default: 'test:all'
24
23
  include Rake::DSL
25
24
 
26
25
  namespace "test" do
27
- task all: [:doctest, :spec]
28
-
29
- desc "Run the doctests"
30
- task :doctest do |_t|
31
- files = Dir['test/*.rdoctest'].sort.map { |f| File.expand_path(f) }.join(' ')
32
- system("#{RUBYDOCTEST} --trace --verbose #{files}") or exit(1)
33
- end
26
+ task all: :spec
34
27
 
35
28
  desc "Prepare a rails application for testing"
36
29
  task :prepare_testapp, :force do |_t, args|
@@ -38,19 +31,19 @@ namespace "test" do
38
31
  FileUtils.remove_entry_secure(TESTAPP_PATH, true)
39
32
  sh %(#{BIN} new #{TESTAPP_PATH} --skip-wizard --skip-bundle)
40
33
  FileUtils.chdir TESTAPP_PATH
41
- sh %(bundle install)
42
- sh %(echo "" >> Gemfile)
43
- sh %(echo "gem 'irt', :group => :development" >> Gemfile) # to make the bundler happy
44
- sh %(echo "gem 'therubyracer'" >> Gemfile)
45
- sh %(echo "gem 'kramdown'" >> Gemfile)
46
- sh %(echo "" > app/models/.gitignore) # because git reset --hard would rm the dir
47
- rm %(.gitignore) # we need to reset everything in a testapp
48
- sh %(git init && git add . && git commit -m "initial commit")
49
- puts %(The testapp has been created in '#{TESTAPP_PATH}')
34
+ sh "bundle install"
35
+ sh "(echo '';
36
+ echo \"gem 'irt', :group => :development\";
37
+ echo \"gem 'therubyracer'\";
38
+ echo \"gem 'kramdown'\") > Gemfile"
39
+ sh "echo '' > app/models/.gitignore" # because git reset --hard would rm the dir
40
+ rm ".gitignore" # we need to reset everything in a testapp
41
+ sh "git init && git add . && git commit -m \"initial commit\""
42
+ puts "The testapp has been created in '#{TESTAPP_PATH}'"
50
43
  else
51
- FileUtils.chdir TESTAPP_PATH
52
- sh %(git add .)
53
- sh %(git reset --hard -q HEAD)
44
+ FileUtils.chdir(TESTAPP_PATH)
45
+ sh "git add ."
46
+ sh "git reset --hard -q HEAD"
54
47
  end
55
48
  end
56
49
  end
@@ -11,7 +11,6 @@ gem "rails", "~> 4.2"
11
11
  gem "responders"
12
12
  gem "rspec"
13
13
  gem "rubocop"
14
- gem "rubydoctest"
15
14
  gem "sqlite3", "~> 1.3.0"
16
15
  gem "yard"
17
16
 
@@ -11,7 +11,6 @@ gem "rails", "~> 5.2"
11
11
  gem "responders"
12
12
  gem "rspec"
13
13
  gem "rubocop"
14
- gem "rubydoctest"
15
14
  gem "sqlite3"
16
15
  gem "yard"
17
16
 
@@ -11,7 +11,6 @@ gem "rails", "~> 6.0"
11
11
  gem "responders"
12
12
  gem "rspec"
13
13
  gem "rubocop"
14
- gem "rubydoctest"
15
14
  gem "sqlite3"
16
15
  gem "yard"
17
16
 
@@ -117,7 +117,6 @@ module DeclareSchema
117
117
  end
118
118
  column_options = {}
119
119
  column_options[:null] = options.delete(:null) || false
120
- column_options[:comment] = options.delete(:comment) if options.has_key?(:comment)
121
120
  column_options[:default] = options.delete(:default) if options.has_key?(:default)
122
121
  column_options[:limit] = options.delete(:limit) if options.has_key?(:limit)
123
122
 
@@ -102,10 +102,6 @@ module DeclareSchema
102
102
  @options[:default]
103
103
  end
104
104
 
105
- def comment
106
- @options[:comment]
107
- end
108
-
109
105
  def same_type?(col_spec)
110
106
  type = sql_type
111
107
  normalized_type = TYPE_SYNONYMS[type] || type
@@ -115,13 +111,6 @@ module DeclareSchema
115
111
 
116
112
  def different_to?(col_spec)
117
113
  !same_type?(col_spec) ||
118
- # we should be able to use col_spec.comment, but col_spec has
119
- # a nil table_name for some strange reason.
120
- (model.table_exists? &&
121
- ActiveRecord::Base.respond_to?(:column_comment) &&
122
- !(col_comment = ActiveRecord::Base.column_comment(col_spec.name, model.table_name)).nil? &&
123
- col_comment != comment
124
- ) ||
125
114
  begin
126
115
  native_type = native_types[type]
127
116
  check_attributes = [:null, :default]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0.pre.1"
5
5
  end
@@ -3,7 +3,7 @@
3
3
  require 'rails/generators/migration'
4
4
  require 'rails/generators/active_record'
5
5
  require 'generators/declare_schema/support/thor_shell'
6
- require_relative '../../../declare_schema/model/field_spec'
6
+ require 'declare_schema/model/field_spec'
7
7
 
8
8
  module DeclareSchema
9
9
  class MigrationGenerator < Rails::Generators::Base
@@ -86,7 +86,7 @@ module DeclareSchema
86
86
  @down = down
87
87
  @migration_class_name = final_migration_name.camelize
88
88
 
89
- migration_template 'migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb"
89
+ migration_template('migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb")
90
90
  if action == 'm'
91
91
  case Rails::VERSION::MAJOR
92
92
  when 4
@@ -118,14 +118,13 @@ module DeclareSchema
118
118
  ActiveRecord::Migrator.new(:up, migrations, ActiveRecord::SchemaMigration).pending_migrations
119
119
  end
120
120
 
121
- if pending_migrations.any?
122
- say "You have #{pending_migrations.size} pending migration#{'s' if pending_migrations.size > 1}:"
123
- pending_migrations.each do |pending_migration|
124
- say format(' %4d %s', pending_migration.version, pending_migration.name)
121
+ pending_migrations.any?.tap do |any|
122
+ if any
123
+ say "You have #{pending_migrations.size} pending migration#{'s' if pending_migrations.size > 1}:"
124
+ pending_migrations.each do |pending_migration|
125
+ say format(' %4d %s', pending_migration.version, pending_migration.name)
126
+ end
125
127
  end
126
- true
127
- else
128
- false
129
128
  end
130
129
  end
131
130
 
@@ -139,10 +138,10 @@ module DeclareSchema
139
138
  loop do
140
139
  if rename_to_choices.empty?
141
140
  say "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}"
142
- resp = ask("Enter 'drop #{t}' to confirm or press enter to keep:")
143
- if resp.strip == "drop #{t}"
141
+ resp = ask("Enter 'drop #{t}' to confirm or press enter to keep:").strip
142
+ if resp == "drop #{t}"
144
143
  break
145
- elsif resp.strip.empty?
144
+ elsif resp.empty?
146
145
  to_drop.delete(t)
147
146
  break
148
147
  else
@@ -151,8 +150,7 @@ module DeclareSchema
151
150
  else
152
151
  say "\nDROP, RENAME or KEEP?: #{kind_str} #{name_prefix}#{t}"
153
152
  say "Rename choices: #{to_create * ', '}"
154
- resp = ask "Enter either 'drop #{t}' or one of the rename choices or press enter to keep:"
155
- resp = resp.strip
153
+ resp = ask("Enter either 'drop #{t}' or one of the rename choices or press enter to keep:").strip
156
154
 
157
155
  if resp == "drop #{t}"
158
156
  # Leave things as they are
@@ -183,3 +181,11 @@ module DeclareSchema
183
181
  end
184
182
  end
185
183
  end
184
+
185
+ module Generators
186
+ module DeclareSchema
187
+ module Migration
188
+ MigrationGenerator = ::DeclareSchema::MigrationGenerator
189
+ end
190
+ end
191
+ end
@@ -106,15 +106,23 @@ module Generators
106
106
 
107
107
  attr_accessor :renames
108
108
 
109
+ # TODO: Add an application callback (maybe an initializer in a special group?) that
110
+ # the application can use to load other models that live in the database, to support DeclareSchema migrations
111
+ # for them.
109
112
  def load_rails_models
113
+ ActiveRecord::Migration.verbose = false
114
+
110
115
  Rails.application.eager_load!
116
+ Rails::Engine.subclasses.each(&:eager_load!)
111
117
  end
112
118
 
113
119
  # Returns an array of model classes that *directly* extend
114
120
  # ActiveRecord::Base, excluding anything in the CGI module
115
121
  def table_model_classes
116
122
  load_rails_models
117
- ActiveRecord::Base.send(:descendants).reject { |c| (c.base_class != c) || c.name.starts_with?("CGI::") }
123
+ ActiveRecord::Base.send(:descendants).select do |klass|
124
+ klass.base_class == klass && !klass.name.starts_with?("CGI::")
125
+ end
118
126
  end
119
127
 
120
128
  def connection
@@ -217,7 +225,7 @@ module Generators
217
225
  end
218
226
  end
219
227
 
220
- def always_ignore_tables
228
+ def self.always_ignore_tables
221
229
  sessions_table =
222
230
  begin
223
231
  if defined?(CGI::Session::ActiveRecordStore::Session) &&
@@ -231,8 +239,8 @@ module Generators
231
239
 
232
240
  [
233
241
  'schema_info',
234
- ActiveRecord::Base.schema_migrations_table_name,
235
- ActiveRecord::Base.internal_metadata_table_name,
242
+ ActiveRecord::Base.try(:schema_migrations_table_name) || 'schema_migrations',
243
+ ActiveRecord::Base.try(:internal_metadata_table_name) || 'ar_internal_metadata',
236
244
  sessions_table
237
245
  ].compact
238
246
  end
@@ -256,7 +264,7 @@ module Generators
256
264
  model_table_names = models_by_table_name.keys
257
265
 
258
266
  to_create = model_table_names - db_tables
259
- to_drop = db_tables - model_table_names - always_ignore_tables
267
+ to_drop = db_tables - model_table_names - self.class.always_ignore_tables
260
268
  to_change = model_table_names
261
269
  to_rename = extract_table_renames!(to_create, to_drop)
262
270
 
@@ -411,7 +419,6 @@ module Generators
411
419
  change_spec[:scale] = spec.scale unless spec.scale.nil?
412
420
  change_spec[:null] = spec.null unless spec.null && col.null
413
421
  change_spec[:default] = spec.default unless spec.default.nil? && col.default.nil?
414
- change_spec[:comment] = spec.comment unless spec.comment.nil? && (col.comment if col.respond_to?(:comment)).nil?
415
422
 
416
423
  changes << "change_column :#{new_table_name}, :#{c}, " +
417
424
  ([":#{spec.sql_type}"] + format_options(change_spec, spec.sql_type, changing: true)).join(", ")
@@ -1,4 +1,4 @@
1
- class <%= @migration_class_name %> < ActiveRecord::Migration<%= Rails::VERSION::MAJOR > 4 ? '[4.2]' : '' %>
1
+ class <%= @migration_class_name %> < ActiveRecord::Migration<%= ('[4.2]' if Rails::VERSION::MAJOR >= 5) %>
2
2
  def self.up
3
3
  <%= @up %>
4
4
  end
@@ -9,9 +9,18 @@ module DeclareSchema
9
9
  private
10
10
 
11
11
  def eval_template(template_name)
12
- source = File.expand_path(find_in_source_paths(template_name))
13
- context = instance_eval('binding')
14
- ERB.new(::File.binread(source), trim_mode: '-').result(context)
12
+ source = File.expand_path(find_in_source_paths(template_name))
13
+ erb = ERB.new(::File.read(source).force_encoding(Encoding::UTF_8), trim_mode: '>')
14
+ erb.filename = source
15
+ begin
16
+ erb.result(binding)
17
+ rescue Exception => ex
18
+ raise ex.class, <<~EOS
19
+ #{ex.message}
20
+ #{erb.src}
21
+ #{ex.backtrace.join("\n ")}
22
+ EOS
23
+ end
15
24
  end
16
25
  end
17
26
  end
@@ -4,6 +4,39 @@ require_relative './eval_template'
4
4
 
5
5
  module DeclareSchema
6
6
  module Support
7
+ class IndentedBuffer
8
+ def initialize(indent: 0)
9
+ @string = +""
10
+ @indent = indent
11
+ @column = 0
12
+ @indent_amount = 2
13
+ end
14
+
15
+ def to_string
16
+ @string
17
+ end
18
+
19
+ def indent!
20
+ @indent += @indent_amount
21
+ yield
22
+ @indent -= @indent_amount
23
+ end
24
+
25
+ def newline!
26
+ @column = 0
27
+ @string << "\n"
28
+ end
29
+
30
+ def <<(str)
31
+ if (difference = @indent - @column) > 0
32
+ @string << ' ' * difference
33
+ end
34
+ @column += difference
35
+ @string << str
36
+ newline!
37
+ end
38
+ end
39
+
7
40
  module Model
8
41
  class << self
9
42
  def included(base)
@@ -26,9 +59,51 @@ module DeclareSchema
26
59
 
27
60
  def inject_declare_schema_code_into_model_file
28
61
  gsub_file(model_path, / # attr_accessible :title, :body\n/m, "")
29
- inject_into_class model_path, class_name do
30
- eval_template('model_injection.rb.erb')
62
+ inject_into_class(model_path, class_name) do
63
+ declare_model_fields_and_associations
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def declare_model_fields_and_associations
70
+ buffer = ::DeclareSchema::Support::IndentedBuffer.new(indent: 2)
71
+ buffer.newline!
72
+ buffer << 'fields do'
73
+ buffer.indent! do
74
+ field_attributes.each do |attribute|
75
+ decl = "%-#{max_attribute_length}s" % attribute.name + ' ' +
76
+ attribute.type.to_sym.inspect +
77
+ case attribute.type.to_s
78
+ when 'string'
79
+ ', limit: 255'
80
+ else
81
+ ''
82
+ end
83
+ buffer << decl
84
+ end
85
+ if options[:timestamps]
86
+ buffer.newline!
87
+ buffer << 'timestamps'
88
+ end
89
+ end
90
+ buffer << 'end'
91
+
92
+ if bts.any?
93
+ buffer.newline!
94
+ bts.each do |bt|
95
+ buffer << "belongs_to #{bt.to_sym.inspect}"
96
+ end
31
97
  end
98
+ if hms.any?
99
+ buffer.newline
100
+ hms.each do |hm|
101
+ buffer << "has_many #{hm.to_sym.inspect}, dependent: :destroy"
102
+ end
103
+ end
104
+ buffer.newline!
105
+
106
+ buffer.to_string
32
107
  end
33
108
 
34
109
  protected
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+ require 'rails/generators'
5
+
6
+ RSpec.describe 'DeclareSchema API' do
7
+ before do
8
+ load File.expand_path('prepare_testapp.rb', __dir__)
9
+ end
10
+
11
+ describe 'example models' do
12
+ it 'generates a model' do
13
+ expect(system("bundle exec rails generate declare_schema:model advert title:string body:text")).to be_truthy
14
+
15
+ # The above will generate the test, fixture and a model file like this:
16
+ # model_declaration = Rails::Generators.invoke('declare_schema:model', ['advert2', 'title:string', 'body:text'])
17
+ # expect(model_declaration.first).to eq([["Advert"], nil, "app/models/advert.rb", nil,
18
+ # [["AdvertTest"], "test/models/advert_test.rb", nil, "test/fixtures/adverts.yml"]])
19
+
20
+ expect(File.read("#{TESTAPP_PATH}/app/models/advert.rb")).to eq(<<~EOS)
21
+ class Advert < #{active_record_base_class}
22
+
23
+ fields do
24
+ title :string, limit: 255
25
+ body :text
26
+ end
27
+
28
+ end
29
+ EOS
30
+ system("rm -rf #{TESTAPP_PATH}/app/models/advert2.rb #{TESTAPP_PATH}/test/models/advert2.rb #{TESTAPP_PATH}/test/fixtures/advert2.rb")
31
+
32
+ # The migration generator uses this information to create a migration.
33
+ # The following creates and runs the migration:
34
+
35
+ expect(system("bundle exec rails generate declare_schema:migration -n -m")).to be_truthy
36
+
37
+ # We're now ready to start demonstrating the API
38
+
39
+ Rails.application.config.autoload_paths += ["#{TESTAPP_PATH}/app/models"]
40
+
41
+ $LOAD_PATH << "#{TESTAPP_PATH}/app/models"
42
+
43
+ unless Rails::VERSION::MAJOR >= 6
44
+ # TODO: get this to work on Travis for Rails 6
45
+ Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
46
+ end
47
+
48
+ require 'advert'
49
+
50
+ ## The Basics
51
+
52
+ # The main feature of DeclareSchema, aside from the migration generator, is the ability to declare rich types for your fields. For example, you can declare that a field is an email address, and the field will be automatically validated for correct email address syntax.
53
+
54
+ ### Field Types
55
+
56
+ # Field values are returned as the type you specify.
57
+
58
+ Advert.destroy_all
59
+
60
+ a = Advert.new(body: "This is the body", id: 1, title: "title")
61
+ expect(a.body).to eq("This is the body")
62
+
63
+ # This also works after a round-trip to the database
64
+
65
+ a.save!
66
+ expect(a.reload.body).to eq("This is the body")
67
+
68
+ ## Names vs. Classes
69
+
70
+ ## Model extensions
71
+
72
+ # DeclareSchema adds a few features to your models.
73
+
74
+ ### `Model.attr_type`
75
+
76
+ # Returns the type (i.e. class) declared for a given field or attribute
77
+
78
+ Advert.connection.schema_cache.clear!
79
+ Advert.reset_column_information
80
+
81
+ expect(Advert.attr_type(:title)).to eq(String)
82
+ expect(Advert.attr_type(:body)).to eq(String)
83
+
84
+ ## Field validations
85
+
86
+ # DeclareSchema gives you some shorthands for declaring some common validations right in the field declaration
87
+
88
+ ### Required fields
89
+
90
+ # The `:required` argument to a field gives a `validates_presence_of`:
91
+
92
+ class AdvertWithRequiredTitle < ActiveRecord::Base
93
+ self.table_name = 'adverts'
94
+
95
+ fields do
96
+ title :string, :required, limit: 255
97
+ end
98
+ end
99
+
100
+ a = AdvertWithRequiredTitle.new
101
+ expect(a.valid? || a.errors.full_messages).to eq(["Title can't be blank"])
102
+ a.id = 2
103
+ a.body = "hello"
104
+ a.title = "Jimbo"
105
+ a.save!
106
+
107
+ ### Unique fields
108
+
109
+ # The `:unique` argument in a field declaration gives `validates_uniqueness_of`:
110
+
111
+ class AdvertWithUniqueTitle < ActiveRecord::Base
112
+ self.table_name = 'adverts'
113
+
114
+ fields do
115
+ title :string, :unique, limit: 255
116
+ end
117
+ end
118
+
119
+ a = AdvertWithUniqueTitle.new :title => "Jimbo", id: 3, body: "hello"
120
+ expect(a.valid? || a.errors.full_messages).to eq(["Title has already been taken"])
121
+ a.title = "Sambo"
122
+ a.save!
123
+ end
124
+ end
125
+ end