rbs_rails 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d9b730da55f64b177f506e6f29efa54bba6878e3cd36b49df76703ac749feb1a
4
+ data.tar.gz: c759ea33ab9071df4806b81f14b5ca125b2f2df04054c4f3a9d2eb6e305c92ab
5
+ SHA512:
6
+ metadata.gz: 7a8fbf409f920b17353906c93d0f86d3c94b270801ab184e2e92b47ceb050659dfaa163cd3d0204e54e970db325826ddb7c6a6df5e380f8342bae760c7d0025c
7
+ data.tar.gz: 06a2387300377054f6ccc1cd9a5414cb64753adfb9ef5e109f95234ec50e8cc5a83b148aa46cc9140cfd47705eb9275c7ca762824564993a6226a77351f33102
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rbs_rails.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # RbsRails
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/rbs_rails`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'rbs_rails'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install rbs_rails
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pocke/rbs_rails.
36
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,126 @@
1
+ class ActiveRecord::Base
2
+ def self.scope: (Symbol, ^(*untyped) -> untyped ) -> void
3
+ | (Symbol) { (*untyped) -> untyped } -> void
4
+ def self.belongs_to: (Symbol, ?untyped, **untyped) -> void
5
+ def self.has_many: (Symbol, ?untyped, **untyped) -> void
6
+ def self.has_one: (Symbol, ?untyped, **untyped) -> void
7
+ def self.transaction: [T] () { () -> T } -> T
8
+ def self.create!: (**untyped) -> instance
9
+ def self.validate: (*untyped) -> void
10
+ def self.validates: (*untyped) -> void
11
+ def self.enum: (Hash[Symbol, untyped]) -> void
12
+
13
+ # callbacks
14
+ def self.after_commit: (*untyped) -> void
15
+ def self.after_create: (*untyped) -> void
16
+ def self.after_destroy: (*untyped) -> void
17
+ def self.after_rollback: (*untyped) -> void
18
+ def self.after_save: (*untyped) -> void
19
+ def self.after_update: (*untyped) -> void
20
+ def self.after_validation: (*untyped) -> void
21
+ def self.around_create: (*untyped) -> void
22
+ def self.around_destroy: (*untyped) -> void
23
+ def self.around_save: (*untyped) -> void
24
+ def self.around_update: (*untyped) -> void
25
+ def self.before_create: (*untyped) -> void
26
+ def self.before_destroy: (*untyped) -> void
27
+ def self.before_save: (*untyped) -> void
28
+ def self.before_update: (*untyped) -> void
29
+ def self.before_validation: (*untyped) -> void
30
+
31
+ def will_save_change_to_attribute?: (String | Symbol attr_name, ?from: untyped, ?to: untyped) -> bool
32
+
33
+ def save!: () -> self
34
+ def save: () -> bool
35
+ def update!: (*untyped) -> self
36
+ def update: (*untyped) -> bool
37
+ def destroy!: () -> self
38
+ def destroy: () -> bool
39
+ def valid?: () -> bool
40
+ def invalid?: () -> bool
41
+ def errors: () -> untyped
42
+ def []: (Symbol) -> untyped
43
+ def []=: (Symbol, untyped) -> untyped
44
+ end
45
+
46
+ class ActiveRecord::Relation
47
+ end
48
+
49
+ class ActiveRecord::Associations::CollectionProxy
50
+ end
51
+
52
+ interface _ActiveRecord_Relation[Model]
53
+ def all: () -> self
54
+ def ids: () -> Array[Integer]
55
+ def none: () -> self
56
+ def pluck: (Symbol | String column) -> Array[untyped]
57
+ | (*Symbol | String columns) -> Array[Array[untyped]]
58
+ def where: (*untyped) -> self
59
+ def not: (*untyped) -> self
60
+ def exists?: (*untyped) -> bool
61
+ def order: (*untyped) -> self
62
+ def group: (*Symbol | String) -> untyped
63
+ def distinct: () -> self
64
+ def or: (self) -> self
65
+ def merge: (self) -> self
66
+ def joins: (*String | Symbol) -> self
67
+ | (Hash[untyped, untyped]) -> self
68
+ def left_joins: (*String | Symbol) -> self
69
+ | (Hash[untyped, untyped]) -> self
70
+ def left_outer_joins: (*String | Symbol) -> self
71
+ | (Hash[untyped, untyped]) -> self
72
+ def includes: (*String | Symbol) -> self
73
+ | (Hash[untyped, untyped]) -> self
74
+ def eager_load: (*String | Symbol) -> self
75
+ | (Hash[untyped, untyped]) -> self
76
+ def preload: (*String | Symbol) -> self
77
+ | (Hash[untyped, untyped]) -> self
78
+ def find_by: (*untyped) -> Model?
79
+ def find_by!: (*untyped) -> Model
80
+ def find: (Integer id) -> Model
81
+ def first: () -> Model
82
+ | (Integer count) -> Array[Model]
83
+ def find_each: (?batch_size: Integer, ?start: Integer, ?finish: Integer, ?error_on_ignore: bool) { (Model) -> void } -> nil
84
+ def find_in_batches: (?batch_size: Integer, ?start: Integer, ?finish: Integer, ?error_on_ignore: bool) { (self) -> void } -> nil
85
+ def destroy_all: () -> untyped
86
+ def delete_all: () -> untyped
87
+ def update_all: (*untyped) -> untyped
88
+ def each: () { (Model) -> void } -> self
89
+ end
90
+
91
+ interface _ActiveRecord_Relation_ClassMethods[Model, Relation]
92
+ def all: () -> Relation
93
+ def ids: () -> Array[Integer]
94
+ def none: () -> Relation
95
+ def pluck: (Symbol | String column) -> Array[untyped]
96
+ | (*Symbol | String columns) -> Array[Array[untyped]]
97
+ def where: (*untyped) -> Relation
98
+ def exists?: (*untyped) -> bool
99
+ def order: (*untyped) -> Relation
100
+ def group: (*Symbol | String) -> untyped
101
+ def distinct: () -> self
102
+ def or: (Relation) -> Relation
103
+ def merge: (Relation) -> Relation
104
+ def joins: (*String | Symbol) -> self
105
+ | (Hash[untyped, untyped]) -> self
106
+ def left_joins: (*String | Symbol) -> self
107
+ | (Hash[untyped, untyped]) -> self
108
+ def left_outer_joins: (*String | Symbol) -> self
109
+ | (Hash[untyped, untyped]) -> self
110
+ def includes: (*String | Symbol) -> self
111
+ | (Hash[untyped, untyped]) -> self
112
+ def eager_load: (*String | Symbol) -> self
113
+ | (Hash[untyped, untyped]) -> self
114
+ def preload: (*String | Symbol) -> self
115
+ | (Hash[untyped, untyped]) -> self
116
+ def find_by: (*untyped) -> Model?
117
+ def find_by!: (*untyped) -> Model
118
+ def find: (Integer id) -> Model
119
+ def first: () -> Model
120
+ | (Integer count) -> Array[Model]
121
+ def find_each: (?batch_size: Integer, ?start: Integer, ?finish: Integer, ?error_on_ignore: bool) { (Model) -> void } -> nil
122
+ def find_in_batches: (?batch_size: Integer, ?start: Integer, ?finish: Integer, ?error_on_ignore: bool) { (self) -> void } -> nil
123
+ def destroy_all: () -> untyped
124
+ def delete_all: () -> untyped
125
+ def update_all: (*untyped) -> untyped
126
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rbs_rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/rbs_rails.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'parser/current'
2
+
3
+ require_relative "rbs_rails/version"
4
+ require_relative 'rbs_rails/active_record'
5
+ require_relative 'rbs_rails/path_helpers'
6
+
7
+ module RbsRails
8
+ class Error < StandardError; end
9
+
10
+ def self.copy_signatures(to:)
11
+ from = Pathname(__dir__) / '../assets/sig/'
12
+ to = Pathname(to)
13
+ FileUtils.cp_r(from, to)
14
+ end
15
+ end
@@ -0,0 +1,272 @@
1
+ module RbsRails
2
+ module ActiveRecord
3
+ def self.class_to_rbs(klass, mode:)
4
+ Generator.new(klass, mode: mode).generate
5
+ end
6
+
7
+ class Generator
8
+ def initialize(klass, mode:)
9
+ @klass = klass
10
+ @mode = mode
11
+ end
12
+
13
+ def generate
14
+ [
15
+ klass_decl,
16
+ relation_decl,
17
+ collection_proxy_decl,
18
+ ].join("\n")
19
+ end
20
+
21
+ private def klass_decl
22
+ <<~RBS
23
+ #{header}
24
+ extend _ActiveRecord_Relation_ClassMethods[#{klass.name}, #{relation_class_name}]
25
+
26
+ #{columns.indent(2)}
27
+ #{associations.indent(2)}
28
+ #{enum_instance_methods.indent(2)}
29
+ #{enum_scope_methods(singleton: true).indent(2)}
30
+ #{scopes(singleton: true).indent(2)}
31
+ end
32
+ RBS
33
+ end
34
+
35
+ private def relation_decl
36
+ <<~RBS
37
+ class #{relation_class_name} < ActiveRecord::Relation
38
+ include _ActiveRecord_Relation[#{klass.name}]
39
+ include Enumerable[#{klass.name}, self]
40
+ #{enum_scope_methods(singleton: false).indent(2)}
41
+ #{scopes(singleton: false).indent(2)}
42
+ end
43
+ RBS
44
+ end
45
+
46
+ private def collection_proxy_decl
47
+ <<~RBS
48
+ class #{klass.name}::ActiveRecord_Associations_CollectionProxy < ActiveRecord::Associations::CollectionProxy
49
+ end
50
+ RBS
51
+ end
52
+
53
+
54
+ private def header
55
+ case mode
56
+ when :extension
57
+ "extension #{klass.name} (RbsRails)"
58
+ when :class
59
+ "class #{klass.name} < #{klass.superclass.name}"
60
+ else
61
+ raise "unexpected mode: #{mode}"
62
+ end
63
+ end
64
+
65
+ private def associations
66
+ [
67
+ has_many,
68
+ has_one,
69
+ belongs_to,
70
+ ].join("\n")
71
+ end
72
+
73
+ private def has_many
74
+ klass.reflect_on_all_associations(:has_many).map do |a|
75
+ "def #{a.name}: () -> #{a.klass.name}::ActiveRecord_Associations_CollectionProxy"
76
+ end.join("\n")
77
+ end
78
+
79
+ private def has_one
80
+ klass.reflect_on_all_associations(:has_one).map do |a|
81
+ type = a.polymorphic? ? 'untyped' : a.klass.name
82
+ "def #{a.name}: () -> #{type}"
83
+ end.join("\n")
84
+ end
85
+
86
+ private def belongs_to
87
+ klass.reflect_on_all_associations(:belongs_to).map do |a|
88
+ type = a.polymorphic? ? 'untyped' : a.klass.name
89
+ "def #{a.name}: () -> #{type}"
90
+ end.join("\n")
91
+ end
92
+
93
+ private def enum_instance_methods
94
+ methods = []
95
+ enum_definitions.each do |hash|
96
+ hash.each do |name, values|
97
+ next if name == :_prefix || name == :_suffix
98
+
99
+ values.each do |label, value|
100
+ value_method_name = enum_method_name(hash, name, label)
101
+ methods << "def #{value_method_name}!: () -> bool"
102
+ methods << "def #{value_method_name}?: () -> bool"
103
+ end
104
+ end
105
+ end
106
+
107
+ methods.join("\n")
108
+ end
109
+
110
+ private def enum_scope_methods(singleton:)
111
+ methods = []
112
+ enum_definitions.each do |hash|
113
+ hash.each do |name, values|
114
+ next if name == :_prefix || name == :_suffix
115
+
116
+ values.each do |label, value|
117
+ value_method_name = enum_method_name(hash, name, label)
118
+ methods << "def #{singleton ? 'self.' : ''}#{value_method_name}: () -> #{relation_class_name}"
119
+ end
120
+ end
121
+ end
122
+ methods.join("\n")
123
+ end
124
+
125
+ private def enum_definitions
126
+ @enum_definitions ||= build_enum_definitions
127
+ end
128
+
129
+ # We need static analysis to detect enum.
130
+ # ActiveRecord has `defined_enums` method,
131
+ # but it does not contain _prefix and _suffix information.
132
+ private def build_enum_definitions
133
+ ast = parse_model_file
134
+ return [] unless ast
135
+
136
+ traverse(ast).map do |node|
137
+ next unless node.type == :send
138
+ next unless node.children[0].nil?
139
+ next unless node.children[1] == :enum
140
+
141
+ definitions = node.children[2]
142
+ next unless definitions
143
+ next unless definitions.type == :hash
144
+ next unless traverse(definitions).all? { |n| [:str, :sym, :int, :hash, :pair, :true, :false].include?(n.type) }
145
+
146
+ code = definitions.loc.expression.source
147
+ code = "{#{code}}" if code[0] != '{'
148
+ eval(code)
149
+ end.compact
150
+ end
151
+
152
+ private def enum_method_name(hash, name, label)
153
+ enum_prefix = hash[:_prefix]
154
+ enum_suffix = hash[:_suffix]
155
+
156
+ if enum_prefix == true
157
+ prefix = "#{name}_"
158
+ elsif enum_prefix
159
+ prefix = "#{enum_prefix}_"
160
+ end
161
+ if enum_suffix == true
162
+ suffix = "_#{name}"
163
+ elsif enum_suffix
164
+ suffix = "_#{enum_suffix}"
165
+ end
166
+
167
+ "#{prefix}#{label}#{suffix}"
168
+ end
169
+
170
+ private def scopes(singleton:)
171
+ ast = parse_model_file
172
+ return '' unless ast
173
+
174
+ traverse(ast).map do |node|
175
+ next unless node.type == :send
176
+ next unless node.children[0].nil?
177
+ next unless node.children[1] == :scope
178
+
179
+ name_node = node.children[2]
180
+ next unless name_node
181
+ next unless name_node.type == :sym
182
+
183
+ name = name_node.children[0]
184
+ body_node = node.children[3]
185
+ next unless body_node
186
+ next unless body_node.type == :block
187
+
188
+ args = args_to_type(body_node.children[1])
189
+ "def #{singleton ? 'self.' : ''}#{name}: (#{args}) -> #{relation_class_name}"
190
+ end.compact.join("\n")
191
+ end
192
+
193
+ private def args_to_type(args_node)
194
+ res = []
195
+ args_node.children.each do |node|
196
+ case node.type
197
+ when :arg
198
+ res << "untyped"
199
+ when :optarg
200
+ res << "?untyped"
201
+ when :kwarg
202
+ res << "#{node.children[0]}: untyped"
203
+ when :kwoptarg
204
+ res << "?#{node.children[0]}: untyped"
205
+ else
206
+ raise "unexpected: #{node}"
207
+ end
208
+ end
209
+ res.join(", ")
210
+ end
211
+
212
+ private def parse_model_file
213
+ return @parse_model_file if defined?(@parse_model_file)
214
+
215
+
216
+ path = Rails.root.join('app/models/', klass.name.underscore + '.rb')
217
+ return @parse_model_file = nil unless path.exist?
218
+ return [] unless path.exist?
219
+
220
+ ast = Parser::CurrentRuby.parse path.read
221
+ return @parse_model_file = nil unless path.exist?
222
+
223
+ @parse_model_file = ast
224
+ end
225
+
226
+ private def traverse(node, &block)
227
+ return to_enum(__method__, node) unless block_given?
228
+
229
+ block.call node
230
+ node.children.each do |child|
231
+ traverse(child, &block) if child.is_a?(Parser::AST::Node)
232
+ end
233
+ end
234
+
235
+ private def relation_class_name
236
+ "#{klass.name}::ActiveRecord_Relation"
237
+ end
238
+
239
+ private def columns
240
+ klass.columns.map do |col|
241
+ "attr_accessor #{col.name} (): #{sql_type_to_class(col.type)}"
242
+ end.join("\n")
243
+ end
244
+
245
+ private def sql_type_to_class(t)
246
+ case t
247
+ when :integer
248
+ Integer.name
249
+ when :string, :text, :uuid
250
+ String.name
251
+ when :datetime
252
+ # TODO
253
+ # ActiveSupport::TimeWithZone.name
254
+ Time.name
255
+ when :boolean
256
+ "TrueClass | FalseClass"
257
+ when :jsonb, :json
258
+ "untyped"
259
+ when :date
260
+ # TODO
261
+ # Date.name
262
+ 'untyped'
263
+ else
264
+ raise "unexpected: #{t.inspect}"
265
+ end
266
+ end
267
+
268
+ private
269
+ attr_reader :klass, :mode
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,31 @@
1
+ module RbsRails
2
+ class PathHelpers
3
+ def self.generate(routes: Rails.application.routes)
4
+ new(routes: Rails.application.routes).generate
5
+ end
6
+
7
+ def initialize(routes:)
8
+ @routes = routes
9
+ end
10
+
11
+ def generate
12
+ methods = helpers.map do |helper|
13
+ # TODO: More restrict argument types
14
+ "def #{helper}: (*untyped) -> String"
15
+ end
16
+
17
+ <<~RBS
18
+ interface _RbsRailsPathHelpers
19
+ #{methods.join("\n").indent(2)}
20
+ end
21
+ RBS
22
+ end
23
+
24
+ private def helpers
25
+ routes.named_routes.helper_names
26
+ end
27
+
28
+ private
29
+ attr_reader :routes
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module RbsRails
2
+ VERSION = "0.1.0"
3
+ end
data/rbs_rails.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ require_relative 'lib/rbs_rails/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rbs_rails"
5
+ spec.version = RbsRails::VERSION
6
+ spec.authors = ["Masataka Pocke Kuwabara"]
7
+ spec.email = ["kuwabara@pocke.me"]
8
+
9
+ spec.summary = %q{A RBS files generator for Rails application}
10
+ spec.description = %q{A RBS files generator for Rails application}
11
+ spec.homepage = "https://github.com/pocke/rbs_rails"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = spec.homepage
16
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_runtime_dependency 'parser'
28
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbs_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Masataka Pocke Kuwabara
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A RBS files generator for Rails application
28
+ email:
29
+ - kuwabara@pocke.me
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - assets/sig/active_record.rbs
39
+ - bin/console
40
+ - bin/setup
41
+ - lib/rbs_rails.rb
42
+ - lib/rbs_rails/active_record.rb
43
+ - lib/rbs_rails/path_helpers.rb
44
+ - lib/rbs_rails/version.rb
45
+ - rbs_rails.gemspec
46
+ homepage: https://github.com/pocke/rbs_rails
47
+ licenses: []
48
+ metadata:
49
+ homepage_uri: https://github.com/pocke/rbs_rails
50
+ source_code_uri: https://github.com/pocke/rbs_rails
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.3.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.2.0.pre1
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: A RBS files generator for Rails application
70
+ test_files: []