declare_schema 0.3.0.pre.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -1
- data/Gemfile.lock +1 -1
- data/lib/declare_schema/model.rb +27 -23
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +1 -1
- data/spec/lib/declare_schema/migration_generator_spec.rb +128 -19
- data/spec/lib/declare_schema/prepare_testapp.rb +2 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3ae0814480b116b32a822887c83612b33458cc910384ae0c2122e547757f141
|
4
|
+
data.tar.gz: 133b7bd6d7d91dae9320a4e8002a8591e43ebb97f57013cbe266cef40a169088
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77b840eb5feb07d38c4fd96f4212d4c81f19c61a2aa2b4bbffb4f4f11594ba55c30e65ca4b77a68e7b29448f7762dba52782f1845307c5bbc02f7603f080c2e8
|
7
|
+
data.tar.gz: 2f85affbd44a6973595883afa01766ede6c950a70c4e783f3064e5c2a59ec5c71a3731d7d484bab133c18c698584077e2c14a8b98c81ce024e2e55bae59dabba
|
data/CHANGELOG.md
CHANGED
@@ -4,10 +4,19 @@ 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.3.0] -
|
7
|
+
## [0.3.0] - 2020-11-02
|
8
8
|
### Added
|
9
|
+
- Added support for `belongs_to optional:`.
|
10
|
+
If given, it is passed through to `ActiveRecord`'s `belong_to`.
|
11
|
+
If not given in Rails 5+, the `optional:` value is set equal to the `null:` value (default: `false`) and that
|
12
|
+
is passed to `ActiveRecord`'s `belong_to`.
|
13
|
+
Similarly, if `null:` is not given, it is inferred from `optional:`.
|
14
|
+
If both are given, their values are respected, even if contradictory;
|
15
|
+
this is a legitimate case when migrating to/from an optional association.
|
9
16
|
- Added a new callback `before_generating_migration` to the `Migrator` that can be
|
10
17
|
defined in order to custom load more models that might be missed by `eager_load!`
|
18
|
+
### Fixed
|
19
|
+
- Migrations are now generated where the `[4.2]` is only applied after `ActiveRecord::Migration` in Rails 5+ (since Rails 4 didn't know about that notation).
|
11
20
|
|
12
21
|
## [0.2.0] - 2020-10-26
|
13
22
|
### Added
|
data/Gemfile.lock
CHANGED
data/lib/declare_schema/model.rb
CHANGED
@@ -106,17 +106,14 @@ module DeclareSchema
|
|
106
106
|
end
|
107
107
|
|
108
108
|
# Extend belongs_to so that it creates a FieldSpec for the foreign key
|
109
|
-
def belongs_to(name,
|
110
|
-
if args.size == 0 || (args.size == 1 && args[0].is_a?(Proc))
|
111
|
-
options = {}
|
112
|
-
args.push(options)
|
113
|
-
elsif args.size == 1
|
114
|
-
options = args[0]
|
115
|
-
else
|
116
|
-
options = args[1]
|
117
|
-
end
|
109
|
+
def belongs_to(name, scope = nil, **options, &block)
|
118
110
|
column_options = {}
|
119
|
-
|
111
|
+
|
112
|
+
column_options[:null] = if options.has_key?(:null)
|
113
|
+
options.delete(:null)
|
114
|
+
elsif options.has_key?(:optional)
|
115
|
+
options[:optional] # infer :null from :optional
|
116
|
+
end || false
|
120
117
|
column_options[:default] = options.delete(:default) if options.has_key?(:default)
|
121
118
|
column_options[:limit] = options.delete(:limit) if options.has_key?(:limit)
|
122
119
|
|
@@ -129,20 +126,27 @@ module DeclareSchema
|
|
129
126
|
fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
|
130
127
|
fk_options[:index_name] = index_options[:name]
|
131
128
|
|
129
|
+
fk = options[:foreign_key]&.to_s || "#{name}_id"
|
130
|
+
|
131
|
+
if !options.has_key?(:optional) && Rails::VERSION::MAJOR >= 5
|
132
|
+
options[:optional] = column_options[:null] # infer :optional from :null
|
133
|
+
end
|
134
|
+
|
132
135
|
fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
136
|
+
|
137
|
+
super(name, scope, options)
|
138
|
+
|
139
|
+
refl = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
|
140
|
+
fkey = refl.foreign_key or raise "Couldn't find foreign_key for #{name} in #{refl.inspect}"
|
141
|
+
declare_field(fkey.to_sym, :integer, column_options)
|
142
|
+
if refl.options[:polymorphic]
|
143
|
+
foreign_type = options[:foreign_type] || "#{name}_type"
|
144
|
+
declare_polymorphic_type_field(foreign_type, column_options)
|
145
|
+
index([foreign_type, fkey], index_options) if index_options[:name] != false
|
146
|
+
else
|
147
|
+
index(fkey, index_options) if index_options[:name] != false
|
148
|
+
options[:constraint_name] = options
|
149
|
+
constraint(fkey, fk_options) if fk_options[:constraint_name] != false
|
146
150
|
end
|
147
151
|
end
|
148
152
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rails'
|
4
|
+
|
3
5
|
RSpec.describe 'DeclareSchema Migration Generator' do
|
4
6
|
before do
|
5
7
|
load File.expand_path('prepare_testapp.rb', __dir__)
|
@@ -53,7 +55,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
53
55
|
|
54
56
|
add_index :adverts, [:id], unique: true, name: 'PRIMARY_KEY'
|
55
57
|
EOS
|
56
|
-
# TODO: ^ add_index should not be there
|
58
|
+
# TODO: ^ TECH-4975 add_index should not be there
|
57
59
|
|
58
60
|
expect(down).to eq(<<~EOS.strip)
|
59
61
|
remove_column :adverts, :body
|
@@ -129,7 +131,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
132
|
-
up
|
134
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
133
135
|
expect(up).to eq("add_column :adverts, :price, :integer, limit: 2")
|
134
136
|
|
135
137
|
# Now run the migration, then change the limit:
|
@@ -154,7 +156,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
154
156
|
end
|
155
157
|
end
|
156
158
|
|
157
|
-
up
|
159
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
158
160
|
expect(up).to eq("add_column :adverts, :price, :decimal")
|
159
161
|
|
160
162
|
# Limits are generally not needed for `text` fields, because by default, `text` fields will use the maximum size
|
@@ -170,7 +172,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
170
172
|
end
|
171
173
|
end
|
172
174
|
|
173
|
-
up
|
175
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
174
176
|
expect(up).to eq(<<~EOS.strip)
|
175
177
|
add_column :adverts, :price, :decimal
|
176
178
|
add_column :adverts, :notes, :text, null: false
|
@@ -194,7 +196,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
194
196
|
end
|
195
197
|
end
|
196
198
|
|
197
|
-
up
|
199
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
198
200
|
expect(up).to eq(<<~EOS.strip)
|
199
201
|
add_column :adverts, :notes, :text, null: false, limit: 4294967295
|
200
202
|
add_column :adverts, :description, :text, null: false, limit: 255
|
@@ -266,7 +268,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
266
268
|
end
|
267
269
|
end
|
268
270
|
|
269
|
-
up
|
271
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
270
272
|
ActiveRecord::Migration.class_eval up
|
271
273
|
Advert.connection.schema_cache.clear!
|
272
274
|
Advert.reset_column_information
|
@@ -278,6 +280,9 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
278
280
|
|
279
281
|
class Category < ActiveRecord::Base; end
|
280
282
|
class Advert < ActiveRecord::Base
|
283
|
+
fields do
|
284
|
+
name :string, limit: 255, null: true
|
285
|
+
end
|
281
286
|
belongs_to :category
|
282
287
|
end
|
283
288
|
|
@@ -298,9 +303,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
298
303
|
|
299
304
|
class Category < ActiveRecord::Base; end
|
300
305
|
class Advert < ActiveRecord::Base
|
306
|
+
fields { }
|
301
307
|
belongs_to :category, foreign_key: "c_id", class_name: 'Category'
|
302
308
|
end
|
303
|
-
up
|
309
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
304
310
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
305
311
|
add_column :adverts, :c_id, :integer, limit: 8, null: false
|
306
312
|
add_index :adverts, [:c_id], name: 'on_c_id'
|
@@ -313,9 +319,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
313
319
|
|
314
320
|
class Category < ActiveRecord::Base; end
|
315
321
|
class Advert < ActiveRecord::Base
|
322
|
+
fields { }
|
316
323
|
belongs_to :category, index: false
|
317
324
|
end
|
318
|
-
up
|
325
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
319
326
|
expect(up.gsub(/\n+/, "\n")).to eq("add_column :adverts, :category_id, :integer, limit: 8, null: false")
|
320
327
|
|
321
328
|
Advert.field_specs.delete(:category_id)
|
@@ -325,9 +332,10 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
325
332
|
|
326
333
|
class Category < ActiveRecord::Base; end
|
327
334
|
class Advert < ActiveRecord::Base
|
335
|
+
fields { }
|
328
336
|
belongs_to :category, index: 'my_index'
|
329
337
|
end
|
330
|
-
up
|
338
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
331
339
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
332
340
|
add_column :adverts, :category_id, :integer, limit: 8, null: false
|
333
341
|
add_index :adverts, [:category_id], name: 'my_index'
|
@@ -372,7 +380,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
372
380
|
title :string, index: true, limit: 255, null: true
|
373
381
|
end
|
374
382
|
end
|
375
|
-
up
|
383
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
376
384
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
377
385
|
add_column :adverts, :title, :string, limit: 255
|
378
386
|
add_index :adverts, [:title], name: 'on_title'
|
@@ -387,7 +395,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
387
395
|
title :string, index: true, unique: true, null: true, limit: 255
|
388
396
|
end
|
389
397
|
end
|
390
|
-
up
|
398
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
391
399
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
392
400
|
add_column :adverts, :title, :string, limit: 255
|
393
401
|
add_index :adverts, [:title], unique: true, name: 'on_title'
|
@@ -402,20 +410,20 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
402
410
|
title :string, index: 'my_index', limit: 255, null: true
|
403
411
|
end
|
404
412
|
end
|
405
|
-
up
|
413
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
406
414
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
407
415
|
add_column :adverts, :title, :string, limit: 255
|
408
416
|
add_index :adverts, [:title], name: 'my_index'
|
409
417
|
EOS
|
410
418
|
|
411
|
-
Advert.index_specs.delete_if {|spec| spec.fields==["title"]}
|
419
|
+
Advert.index_specs.delete_if { |spec| spec.fields==["title"] }
|
412
420
|
|
413
421
|
# You can ask for an index outside of the fields block
|
414
422
|
|
415
423
|
class Advert < ActiveRecord::Base
|
416
424
|
index :title
|
417
425
|
end
|
418
|
-
up
|
426
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
419
427
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
420
428
|
add_column :adverts, :title, :string, limit: 255
|
421
429
|
add_index :adverts, [:title], name: 'on_title'
|
@@ -428,7 +436,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
428
436
|
class Advert < ActiveRecord::Base
|
429
437
|
index :title, unique: true, name: 'my_index'
|
430
438
|
end
|
431
|
-
up
|
439
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
432
440
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
433
441
|
add_column :adverts, :title, :string, limit: 255
|
434
442
|
add_index :adverts, [:title], unique: true, name: 'my_index'
|
@@ -441,7 +449,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
441
449
|
class Advert < ActiveRecord::Base
|
442
450
|
index [:title, :category_id]
|
443
451
|
end
|
444
|
-
up
|
452
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
445
453
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
446
454
|
add_column :adverts, :title, :string, limit: 255
|
447
455
|
add_index :adverts, [:title, :category_id], name: 'on_title_and_category_id'
|
@@ -544,7 +552,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
544
552
|
title :string, default: "Untitled", limit: 255, null: true
|
545
553
|
end
|
546
554
|
end
|
547
|
-
up
|
555
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
548
556
|
ActiveRecord::Migration.class_eval(up)
|
549
557
|
|
550
558
|
class FancyAdvert < Advert
|
@@ -613,7 +621,7 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
613
621
|
created_at :datetime
|
614
622
|
end
|
615
623
|
end
|
616
|
-
up
|
624
|
+
up = Generators::DeclareSchema::Migration::Migrator.run(adverts: :ads).first
|
617
625
|
expect(up.gsub(/\n+/, "\n")).to eq(<<~EOS.strip)
|
618
626
|
rename_table :adverts, :ads
|
619
627
|
add_column :ads, :created_at, :datetime, null: false
|
@@ -682,5 +690,106 @@ RSpec.describe 'DeclareSchema Migration Generator' do
|
|
682
690
|
ActiveRecord::Migration.class_eval(up)
|
683
691
|
expect(Ad.field_specs['company'].options[:validates].inspect).to eq("{:presence=>true, :uniqueness=>{:case_sensitive=>false}}")
|
684
692
|
end
|
685
|
-
end
|
686
693
|
|
694
|
+
if Rails::VERSION::MAJOR >= 5
|
695
|
+
describe 'belongs_to' do
|
696
|
+
before do
|
697
|
+
unless defined?(AdCategory)
|
698
|
+
class AdCategory < ActiveRecord::Base
|
699
|
+
fields { }
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
class Advert < ActiveRecord::Base
|
704
|
+
fields do
|
705
|
+
name :string, limit: 255, null: true
|
706
|
+
category_id :integer, limit: 8
|
707
|
+
nullable_category_id :integer, limit: 8, null: true
|
708
|
+
end
|
709
|
+
end
|
710
|
+
up = Generators::DeclareSchema::Migration::Migrator.run.first
|
711
|
+
ActiveRecord::Migration.class_eval(up)
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'passes through optional: when given' do
|
715
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
716
|
+
self.table_name = 'adverts'
|
717
|
+
fields { }
|
718
|
+
reset_column_information
|
719
|
+
belongs_to :ad_category, optional: true
|
720
|
+
end
|
721
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: true)
|
722
|
+
end
|
723
|
+
|
724
|
+
describe 'contradictory settings' do # contradictory settings are ok during migration
|
725
|
+
it 'passes through optional: true, null: false' do
|
726
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
727
|
+
self.table_name = 'adverts'
|
728
|
+
fields { }
|
729
|
+
reset_column_information
|
730
|
+
belongs_to :ad_category, optional: true, null: false
|
731
|
+
end
|
732
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: true)
|
733
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(false)
|
734
|
+
end
|
735
|
+
|
736
|
+
it 'passes through optional: false, null: true' do
|
737
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
738
|
+
self.table_name = 'adverts'
|
739
|
+
fields { }
|
740
|
+
reset_column_information
|
741
|
+
belongs_to :ad_category, optional: false, null: true
|
742
|
+
end
|
743
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: false)
|
744
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(true)
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
[false, true].each do |nullable|
|
749
|
+
context "nullable=#{nullable}" do
|
750
|
+
it 'infers optional: from null:' do
|
751
|
+
eval <<~EOS
|
752
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
753
|
+
fields { }
|
754
|
+
belongs_to :ad_category, null: #{nullable}
|
755
|
+
end
|
756
|
+
EOS
|
757
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: nullable)
|
758
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
759
|
+
end
|
760
|
+
|
761
|
+
it 'infers null: from optional:' do
|
762
|
+
eval <<~EOS
|
763
|
+
class AdvertBelongsTo < ActiveRecord::Base
|
764
|
+
fields { }
|
765
|
+
belongs_to :ad_category, optional: #{nullable}
|
766
|
+
end
|
767
|
+
EOS
|
768
|
+
expect(AdvertBelongsTo.reflections['ad_category'].options).to eq(optional: nullable)
|
769
|
+
expect(AdvertBelongsTo.field_specs['ad_category_id'].options&.[](:null)).to eq(nullable)
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
describe 'migration base class' do
|
777
|
+
it 'adapts to Rails 4' do
|
778
|
+
class Advert < active_record_base_class.constantize
|
779
|
+
fields do
|
780
|
+
title :string, limit: 100
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
Rails::Generators.invoke('declare_schema:migration', %w[-n -m])
|
785
|
+
|
786
|
+
migrations = Dir.glob('db/migrate/*declare_schema_migration*.rb')
|
787
|
+
expect(migrations.size).to eq(1), migrations.inspect
|
788
|
+
|
789
|
+
migration_content = File.read(migrations.first)
|
790
|
+
first_line = migration_content.split("\n").first
|
791
|
+
base_class = first_line.split(' < ').last
|
792
|
+
expect(base_class).to eq("(Rails::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration)")
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end
|
@@ -18,6 +18,8 @@ require "#{TESTAPP_PATH}/config/environment"
|
|
18
18
|
require 'rails/generators'
|
19
19
|
Rails::Generators.configure!(Rails.application.config.generators)
|
20
20
|
|
21
|
+
ActiveRecord::Base.connection.schema_cache.clear!
|
22
|
+
|
21
23
|
(ActiveRecord::Base.connection.tables - Generators::DeclareSchema::Migration::Migrator.always_ignore_tables).each do |table|
|
22
24
|
ActiveRecord::Base.connection.execute("DROP TABLE #{ActiveRecord::Base.connection.quote_table_name(table)}")
|
23
25
|
end
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: declare_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.0
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Invoca Development adapted from hobo_fields by Tom Locke
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2020-11-02 00:00:00.000000000 Z
|
@@ -84,7 +84,7 @@ homepage: https://github.com/Invoca/declare_schema
|
|
84
84
|
licenses: []
|
85
85
|
metadata:
|
86
86
|
allowed_push_host: https://rubygems.org
|
87
|
-
post_install_message:
|
87
|
+
post_install_message:
|
88
88
|
rdoc_options: []
|
89
89
|
require_paths:
|
90
90
|
- lib
|
@@ -100,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
100
|
version: 1.3.6
|
101
101
|
requirements: []
|
102
102
|
rubygems_version: 3.0.3
|
103
|
-
signing_key:
|
103
|
+
signing_key:
|
104
104
|
specification_version: 4
|
105
105
|
summary: Database migration generator for Rails
|
106
106
|
test_files: []
|