solargraph-rails 0.2.2.pre.4 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  3. data/.github/workflows/ruby.yml +36 -0
  4. data/.gitignore +8 -0
  5. data/.solargraph.yml +19 -0
  6. data/CHANGELOG.md +19 -0
  7. data/DEVELOPMENT.md +83 -0
  8. data/README.md +5 -3
  9. data/lib/solargraph/rails/annotate.rb +44 -0
  10. data/lib/solargraph/rails/annotations.rb +50 -0
  11. data/lib/solargraph/rails/autoload.rb +53 -0
  12. data/lib/solargraph/rails/debug.rb +30 -0
  13. data/lib/solargraph/rails/delegate.rb +42 -0
  14. data/lib/solargraph/rails/devise.rb +48 -0
  15. data/lib/solargraph/rails/model.rb +100 -0
  16. data/lib/solargraph/rails/rails_api.rb +98 -0
  17. data/lib/solargraph/rails/schema.rb +100 -0
  18. data/lib/solargraph/rails/storage.rb +48 -0
  19. data/lib/solargraph/rails/types.yml +24 -0
  20. data/lib/solargraph/rails/util.rb +68 -0
  21. data/lib/solargraph/rails/version.rb +1 -1
  22. data/lib/solargraph/rails/walker.rb +89 -0
  23. data/lib/solargraph-rails.rb +57 -22
  24. data/script/generate_definitions.rb +134 -0
  25. data/solargraph-rails.gemspec +24 -22
  26. metadata +26 -16
  27. data/Gemfile.lock +0 -109
  28. data/lib/solargraph/rails/files_loader.rb +0 -16
  29. data/lib/solargraph/rails/meta_source/association/belongs_to_matcher.rb +0 -24
  30. data/lib/solargraph/rails/meta_source/association/has_and_belongs_to_many_matcher.rb +0 -24
  31. data/lib/solargraph/rails/meta_source/association/has_many_matcher.rb +0 -24
  32. data/lib/solargraph/rails/meta_source/association/has_one_matcher.rb +0 -24
  33. data/lib/solargraph/rails/pin_creator.rb +0 -117
  34. data/lib/solargraph/rails/ruby_parser.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b1512086d26a2b7a883013b14d919163ea9f3155b67f6cf87ad956c46319857
4
- data.tar.gz: f88b8cd47da267a95cb418d339e37ad0e25b975e5952eb19d7b6fb9c1d353d42
3
+ metadata.gz: 134394397585a1df968b364e98d20b8e38a2fb6294833bfe8bbcfb81b13c545f
4
+ data.tar.gz: cb4e7fe2906a0769c95e8a3e7de08b6d0188b4dd8d509db2e001006e9367e6db
5
5
  SHA512:
6
- metadata.gz: c09211c20651d6011f50b1aa6bc3b0c1ab961e868d9bfebdf25eb1c4ac2d0972c2735cddc5bbba8294726e8e63a4b3ad1c4c79db0b9c8d5d7be4aeb0c2dd5caf
7
- data.tar.gz: 3be48e28a9eba627f15ff1543d933673f89abcf7b2ac268f1f69ab8414a21dac0cc5e6ae1b518148f165849cec1a0b224d8fa52b1dc94e50585be044ca282a32
6
+ metadata.gz: c7acc8b51be9a665bcca385a67bcee5eed0ffd9e697c155bfab4e5e94dcd691cb4ccab4f04c09306166c99e9802e3276e9895374540d3df7b3c979c59be582d4
7
+ data.tar.gz: 4de5589904080422ed72fec8d76e4679a53df812f551c7e99d5d0e6fc7a6cabefe5256a6403dab04ae96e0fd79ec85c92f7a171502202afdb7403e30b266ee04
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Debug log**
27
+
28
+ Run the following command in the project you are having problems:
29
+ ```
30
+ ruby -r'solargraph-rails' -e 'Solargraph::Rails::Debug.run()'
31
+ ```
32
+
33
+ and paste the output here
@@ -0,0 +1,36 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ workflow_dispatch: {}
12
+ pull_request:
13
+ branches: [master]
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ matrix:
20
+ ruby-version:
21
+ - "2.7"
22
+ - "3.0"
23
+ - "3.1"
24
+
25
+ steps:
26
+ - uses: actions/checkout@v2
27
+ - uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby-version }}
30
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
31
+ - name: Install Rails 5 deps
32
+ run: (cd spec/rails5; bundle install; yard gems)
33
+ - name: Install Rails 6 deps
34
+ run: (cd spec/rails6; bundle install; yard gems)
35
+ - name: Run tests
36
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -6,6 +6,14 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .DS_Store
10
+ spec/rails5/log/
11
+ spec/rails5/tmp/
12
+ spec/rails6/log/
13
+ spec/rails6/tmp/
14
+ .projections.json
15
+ .byebug_history
9
16
 
10
17
  # rspec failure tracking
11
18
  .rspec_status
19
+ Gemfile.lock
data/.solargraph.yml ADDED
@@ -0,0 +1,19 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - test/**/*
6
+ - vendor/**/*
7
+ - ".bundle/**/*"
8
+ require: []
9
+ domains: []
10
+ reporters: []
11
+ formatter:
12
+ rubocop:
13
+ cops: safe
14
+ except: []
15
+ only: []
16
+ extra_args: []
17
+ require_paths: []
18
+ plugins: []
19
+ max_files: 5000
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Changes
4
4
 
5
+ ### v1.0.0.pre.1
6
+
7
+ https://github.com/alisnic/solargraph-arc was merged, with the following features:
8
+ - fixes autocompletion for multi-level classes defined in 1 line `class Foo::Bar::Baz`. See https://github.com/castwide/solargraph/issues/506
9
+ - autocomplete database columns by parsing db/schema.rb
10
+ - autocomplete of model relations
11
+ - parsing of `delegate` calls
12
+ - completions for methods generated by Devise
13
+ - better support for running solargraph outside bundle
14
+ - better completion inside controllers. `request`, `response`, `params`, etc.
15
+ - autocomplete inside routes.rb
16
+ - autocomplete inside migrations
17
+ - completions for methods generated by ActiveStorage
18
+ - better ActiveRecord completions
19
+
20
+ ### v0.3.0
21
+ * Require String inflection monkeypatches directly to avoid error from ActiveSupport v7
22
+ * Remove Gemfile.lock from version control
23
+
5
24
  ### v0.2.2.pre.4
6
25
  * Loosen the dependency requirements
7
26
 
data/DEVELOPMENT.md ADDED
@@ -0,0 +1,83 @@
1
+ # Solargraph-Rails development guide
2
+
3
+ ## Contributing
4
+
5
+ 1. create fork and clone the repo
6
+ 2. install gem deps `bundle install`
7
+ 3. install dummy rails5 app deps and build its yard cache
8
+
9
+ ```
10
+ $ cd spec/rails5
11
+ $ bundle install && yard gems
12
+ $ cd ../../
13
+ ```
14
+
15
+ 3. install dummy rails6 app deps and build its yard cache
16
+
17
+ ```
18
+ $ cd spec/rails6
19
+ $ bundle install && yard gems
20
+ $ cd ../../
21
+ ```
22
+ 4. now tests should pass locally and you can try different changes
23
+ 5. sumbit PR
24
+
25
+ ## Completion coverage tracking
26
+
27
+ Solargraph-Rails uses a [set of yaml files](https://github.com/iftheshoefritz/solargraph-rails/tree/master/spec/definitions) to track coverage of found completions.
28
+ Those yaml files are generated at runtime from a dummy [rails5](https://github.com/iftheshoefritz/solargraph-rails/tree/master/spec/rails5) or [rails6](https://github.com/iftheshoefritz/solargraph-rails/tree/master/spec/rails6) app.
29
+
30
+ The main goal is to catch any regressions in case of any change. In case a method completion is marked completed and it is not found in solargraph completions, the tests will fail.
31
+
32
+ ### Checking coverage
33
+
34
+ To see what is completion coverage for solargraph-rails, run the tests with the `PRINT_STATS=true` environment variable:
35
+
36
+ ```
37
+ $ PRINT_STATS=true bundle exec rspec
38
+ ```
39
+
40
+ What you will see in test output is reported coverage for classes that are tracked:
41
+
42
+ ```
43
+ {:class_name=>"ActiveRecord::Base", :total=>800, :covered=>321, :typed=>10, :percent_covered=>40.1, :percent_typed=>1.3}
44
+ provides completions for ActiveRecord::Base
45
+ ```
46
+
47
+ ### Updating assertions
48
+
49
+ In case an improvement is made, and more completions are found then being asserted, tests will throw a warning:
50
+
51
+ ```
52
+ ActionDispatch::Routing::Mapper.try! is marked as skipped in spec/definitions/rails5/routes.yml, but is actually present.
53
+ Consider setting skip=false
54
+ provides completions for ActionDispatch::Routing::Mapper
55
+ ```
56
+
57
+ In this case there are 2 options:
58
+ 1. Manually updating yml file and setting `skip: false` for that method
59
+ 2. Updating yml file in place by passing `update: true` to assertion:
60
+
61
+ ```diff
62
+ assert_matches_definition(
63
+ map,
64
+ 'ActionDispatch::Routing::Mapper',
65
+ 'rails5/routes',
66
+ + update: true
67
+ )
68
+ end
69
+ ```
70
+
71
+ In case of option 2, don't forget to remove the flag after yml file has been updated. Also review git diff, to make sure that no regressions have been set (skip=true was set for entries which previously had skip=false)
72
+
73
+ ### Generating assertions
74
+
75
+ In case a new set of assertion files has to be created (for a new Rails version for example), a script can be used - https://github.com/iftheshoefritz/solargraph-rails/blob/master/script/generate_definitions.rb.
76
+
77
+ All you have to do is to go to the same Rails app root, and execute the script:
78
+
79
+ ```
80
+ ruby path/to/generate_definitions.rb
81
+ ```
82
+
83
+ Make sure to review the script and uncomment relevant parts
data/README.md CHANGED
@@ -52,23 +52,25 @@ Solargraph won't know about attributes that you add during a session. Restart yo
52
52
  For my setup with Emacs, that means running `M-x lsp-workspace-restart`, YMMV in other editors.
53
53
 
54
54
  ## Associations (experimental)
55
- There is very hacky and simplistic support for `belongs_to` and `has_many` macros, if you are willing to use `gem install solargraph-rails --pre`:
55
+ There is simplistic support for `belongs_to` and `has_many` macros:
56
56
 
57
57
  ![Experimental autocomplete and go to definition of associations](assets/solar_rails_associations.gif)
58
58
 
59
59
  ## Known issues
60
60
  This project is being used to write production code by the maintainer, but it is still WIP. Check out the issues tab and contribute if you are interested.
61
61
 
62
+ Association support is slightly less functional in Rails 7.
63
+
62
64
  ## Installation
63
65
 
64
- ### Install `solargraph` v0.40+ and `solargraph_rails` locally
66
+ ### Install `solargraph` and `solargraph-rails` locally
65
67
 
66
68
  Typically gems like these are not installed via the Gemfile, because most projects have more than one contributor and other contributors might have different setups for their editors in mind. Instead you need to use `gem install`.
67
69
 
68
70
  `gem install solargraph-rails`
69
71
 
70
72
  #### Alternative: using bundler
71
- If you do want to use bundler, add `gem 'solargraph-rails', '~> 0.2.0'`
73
+ If you do want to use bundler, add `gem 'solargraph-rails'`
72
74
 
73
75
  ### Add `solargraph-rails` to your `.solargraph.yml`
74
76
 
@@ -0,0 +1,44 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Annotate
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def self.reset
9
+ @instance = nil
10
+ end
11
+
12
+ def initialize
13
+ @schema_present = File.exist?('db/schema.rb')
14
+ end
15
+
16
+ def process(source_map, ns)
17
+ return [] if @schema_present
18
+ return [] unless source_map.filename.include?('app/models')
19
+
20
+ pins = []
21
+ walker = Walker.from_source(source_map.source)
22
+ walker.comments.each do |_, snip|
23
+ name, type = snip.text.gsub(/[\(\),:\d]/, '').split[1..2]
24
+
25
+ next unless name && type
26
+
27
+ ruby_type = Schema::RUBY_TYPES[type.to_sym]
28
+ next unless ruby_type
29
+
30
+ pins <<
31
+ Util.build_public_method(
32
+ ns,
33
+ name,
34
+ types: [ruby_type],
35
+ location:
36
+ Solargraph::Location.new(source_map.filename, snip.range)
37
+ )
38
+ end
39
+
40
+ pins
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # The following comments fill some of the gaps in Solargraph's understanding of
2
+ # Rails apps. Since they're all in YARD, they get mapped in Solargraph but
3
+ # ignored at runtime.
4
+ #
5
+ # You can put this file anywhere in the project, as long as it gets included in
6
+ # the workspace maps. It's recommended that you keep it in a standalone file
7
+ # instead of pasting it into an existing one.
8
+ #
9
+ # @!parse
10
+ # class ActionController::Base
11
+ # include ActionController::MimeResponds
12
+ # include ActionController::Redirecting
13
+ # include ActionController::Cookies
14
+ # include AbstractController::Rendering
15
+ # extend ActiveSupport::Callbacks::ClassMethods
16
+ # extend ActiveSupport::Rescuable::ClassMethods
17
+ # extend AbstractController::Callbacks::ClassMethods
18
+ # extend ActionController::RequestForgeryProtection::ClassMethods
19
+ # end
20
+ # class ActionDispatch::Routing::Mapper
21
+ # include ActionDispatch::Routing::Mapper::Base
22
+ # include ActionDispatch::Routing::Mapper::HttpHelpers
23
+ # include ActionDispatch::Routing::Mapper::Redirection
24
+ # include ActionDispatch::Routing::Mapper::Scoping
25
+ # include ActionDispatch::Routing::Mapper::Concerns
26
+ # include ActionDispatch::Routing::Mapper::Resources
27
+ # include ActionDispatch::Routing::Mapper::CustomUrls
28
+ # end
29
+ # class Rails
30
+ # # @return [Rails::Application]
31
+ # def self.application; end
32
+ # end
33
+ # class Rails::Application
34
+ # # @return [ActionDispatch::Routing::RouteSet]
35
+ # def routes; end
36
+ # end
37
+ # class ActionDispatch::Routing::RouteSet
38
+ # # @yieldself [ActionDispatch::Routing::Mapper]
39
+ # def draw; end
40
+ # end
41
+ # class ActiveRecord::Base
42
+ # extend ActiveRecord::QueryMethods
43
+ # extend ActiveRecord::FinderMethods
44
+ # extend ActiveRecord::Associations::ClassMethods
45
+ # extend ActiveRecord::Inheritance::ClassMethods
46
+ # extend ActiveRecord::ModelSchema::ClassMethods
47
+ # extend ActiveRecord::Transactions::ClassMethods
48
+ # extend ActiveRecord::Scoping::Named::ClassMethods
49
+ # include ActiveRecord::Persistence
50
+ # end
@@ -0,0 +1,53 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Autoload
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def process(source_map, ns, ds)
9
+ return [] unless ds.size == 1 && ns.path.include?('::')
10
+ Solargraph.logger.debug(
11
+ "[Rails][Autoload] seeding class tree for #{ns.path}"
12
+ )
13
+
14
+ root_ns = source_map.pins.find { |p| p.path == '' }
15
+ namespace_stubs(root_ns, ns)
16
+ end
17
+
18
+ def namespace_stubs(root_ns, ns)
19
+ parts = ns.path.split('::')
20
+
21
+ candidates =
22
+ parts
23
+ .each_with_index
24
+ .reduce([]) { |acc, (_, i)| acc + [parts[0..i].join('::')] }
25
+ .reject { |el| el == ns.path }
26
+
27
+ previous_ns = root_ns
28
+ pins = []
29
+
30
+ parts[0..-2].each_with_index do |name, i|
31
+ gates = candidates[0..i].reverse + ['']
32
+ path = gates.first
33
+ next if path == ns.path
34
+
35
+ previous_ns =
36
+ Solargraph::Pin::Namespace.new(
37
+ type: :class,
38
+ location: ns.location,
39
+ closure: previous_ns,
40
+ name: name,
41
+ comments: ns.comments,
42
+ visibility: :public,
43
+ gates: gates[1..-1]
44
+ )
45
+
46
+ pins << previous_ns
47
+ end
48
+
49
+ pins
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Debug
4
+ def self.run(query = nil)
5
+ self.new.run(query)
6
+ end
7
+
8
+ def run(query)
9
+ Solargraph.logger.level = Logger::DEBUG
10
+
11
+ api_map = Solargraph::ApiMap.load('./')
12
+
13
+ puts "Ruby version: #{RUBY_VERSION}"
14
+ puts "Solargraph version: #{Solargraph::VERSION}"
15
+ puts "Solargraph Rails version: #{Solargraph::Rails::VERSION}"
16
+
17
+ return unless query
18
+
19
+ puts "Known methods for #{query}"
20
+
21
+ pin = api_map.pins.find { |p| p.path == query }
22
+ return unless pin
23
+
24
+ api_map
25
+ .get_complex_type_methods(pin.return_type)
26
+ .each { |pin| puts "- #{pin.path}" }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Delegate
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def process(source_map, ns)
9
+ return [] unless source_map.code.include?('delegate')
10
+
11
+ walker = Walker.from_source(source_map.source)
12
+ pins = []
13
+
14
+ walker.on :send, [nil, :delegate] do |ast|
15
+ methods =
16
+ ast.children[2..-1]
17
+ .map { |c| c.children.first }
18
+ .select { |s| s.is_a?(Symbol) }
19
+
20
+ methods.each do |meth|
21
+ pins <<
22
+ Util.build_public_method(
23
+ ns,
24
+ meth.to_s,
25
+ location: Util.build_location(ast, ns.filename)
26
+ )
27
+ end
28
+ end
29
+
30
+ walker.walk
31
+
32
+ if pins.any?
33
+ Solargraph.logger.debug(
34
+ "[Rails][Delegate] added #{pins.map(&:name)} to #{ns.path}"
35
+ )
36
+ end
37
+
38
+ pins
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Devise
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def process(source_map, ns)
9
+ if source_map.filename.include?('app/models')
10
+ process_model(source_map.source, ns)
11
+ else
12
+ []
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def process_model(source, ns)
19
+ walker = Walker.from_source(source)
20
+ pins = []
21
+
22
+ walker.on :send, [nil, :devise] do |ast|
23
+ modules =
24
+ ast.children[2..-1]
25
+ .map { |c| c.children.first }
26
+ .select { |s| s.is_a?(Symbol) }
27
+
28
+ modules.each do |mod|
29
+ pins <<
30
+ Util.build_module_include(
31
+ ns,
32
+ "Devise::Models::#{mod.to_s.capitalize}",
33
+ Util.build_location(ast, ns.filename)
34
+ )
35
+ end
36
+ end
37
+
38
+ walker.walk
39
+ if pins.any?
40
+ Solargraph.logger.debug(
41
+ "[Rails][Devise] added #{pins.map(&:name)} to #{ns.path}"
42
+ )
43
+ end
44
+ pins
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,100 @@
1
+ module Solargraph
2
+ module Rails
3
+ class Model
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def process(source_map, ns)
9
+ return [] unless source_map.filename.include?('app/models')
10
+
11
+ walker = Walker.from_source(source_map.source)
12
+ pins = []
13
+
14
+ walker.on :send, [nil, :belongs_to] do |ast|
15
+ pins << singular_association(ns, ast)
16
+ end
17
+
18
+ walker.on :send, [nil, :has_one] do |ast|
19
+ pins << singular_association(ns, ast)
20
+ end
21
+
22
+ walker.on :send, [nil, :has_many] do |ast|
23
+ pins << plural_association(ns, ast)
24
+ end
25
+
26
+ walker.on :send, [nil, :has_and_belongs_to_many] do |ast|
27
+ pins << plural_association(ns, ast)
28
+ end
29
+
30
+ walker.on :send, [nil, :scope] do |ast|
31
+ name = ast.children[2].children.last
32
+
33
+ method_pin =
34
+ Util.build_public_method(
35
+ ns,
36
+ name.to_s,
37
+ types: ns.return_type.map(&:tag),
38
+ scope: :class,
39
+ location: Util.build_location(ast, ns.filename)
40
+ )
41
+
42
+ if ast.children.last.type == :block
43
+ location = ast.children.last.location
44
+ block_pin =
45
+ source_map.locate_block_pin(location.line, location.column)
46
+ method_pin.parameters.concat(block_pin.parameters.clone)
47
+ end
48
+ pins << method_pin
49
+ end
50
+
51
+ walker.walk
52
+ if pins.any?
53
+ Solargraph.logger.debug(
54
+ "[Rails][Model] added #{pins.map(&:name)} to #{ns.path}"
55
+ )
56
+ end
57
+ pins
58
+ end
59
+
60
+ def plural_association(ns, ast)
61
+ relation_name = ast.children[2].children.first
62
+ class_name =
63
+ extract_custom_class_name(ast) ||
64
+ relation_name.to_s.singularize.camelize
65
+
66
+ Util.build_public_method(
67
+ ns,
68
+ relation_name.to_s,
69
+ types: ["ActiveRecord::Associations::CollectionProxy<#{class_name}>"],
70
+ location: Util.build_location(ast, ns.filename)
71
+ )
72
+ end
73
+
74
+ def singular_association(ns, ast)
75
+ relation_name = ast.children[2].children.first
76
+ class_name =
77
+ extract_custom_class_name(ast) || relation_name.to_s.camelize
78
+
79
+ Util.build_public_method(
80
+ ns,
81
+ relation_name.to_s,
82
+ types: [class_name],
83
+ location: Util.build_location(ast, ns.filename)
84
+ )
85
+ end
86
+
87
+ def extract_custom_class_name(ast)
88
+ options = ast.children[3..-1].find { |n| n.type == :hash }
89
+ return unless options
90
+
91
+ class_name_pair =
92
+ options.children.find do |n|
93
+ n.children[0].deconstruct == %i[sym class_name] &&
94
+ n.children[1].type == :str
95
+ end
96
+ class_name_pair && class_name_pair.children.last.children.last
97
+ end
98
+ end
99
+ end
100
+ end