rbs_activerecord 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +22 -0
- data/.vscode/extensions.json +6 -0
- data/.vscode/settings.json +12 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +14 -0
- data/Steepfile +9 -0
- data/lib/generators/rbs_activerecord/install_generator.rb +21 -0
- data/lib/rbs_activerecord/generator/active_storage/instance_methods.rb +57 -0
- data/lib/rbs_activerecord/generator/active_storage/scopes.rb +39 -0
- data/lib/rbs_activerecord/generator/associations.rb +118 -0
- data/lib/rbs_activerecord/generator/attributes.rb +95 -0
- data/lib/rbs_activerecord/generator/delegated_type/instance_methods.rb +66 -0
- data/lib/rbs_activerecord/generator/delegated_type/scopes.rb +44 -0
- data/lib/rbs_activerecord/generator/enum/base.rb +63 -0
- data/lib/rbs_activerecord/generator/enum/instance_methods.rb +51 -0
- data/lib/rbs_activerecord/generator/enum/scopes.rb +51 -0
- data/lib/rbs_activerecord/generator/pluck_overloads.rb +41 -0
- data/lib/rbs_activerecord/generator/scopes.rb +99 -0
- data/lib/rbs_activerecord/generator/secure_password.rb +54 -0
- data/lib/rbs_activerecord/generator.rb +143 -0
- data/lib/rbs_activerecord/model.rb +33 -0
- data/lib/rbs_activerecord/parser/evaluator.rb +48 -0
- data/lib/rbs_activerecord/parser/visitor.rb +67 -0
- data/lib/rbs_activerecord/parser.rb +30 -0
- data/lib/rbs_activerecord/rake_task.rb +61 -0
- data/lib/rbs_activerecord/utils.rb +58 -0
- data/lib/rbs_activerecord/version.rb +5 -0
- data/lib/rbs_activerecord.rb +27 -0
- data/rbs_collection.lock.yaml +352 -0
- data/rbs_collection.yaml +18 -0
- data/sig/generators/rbs_activerecord/install_generator.rbs +7 -0
- data/sig/rbs_activerecord/generator/active_storage/instance_methods.rbs +23 -0
- data/sig/rbs_activerecord/generator/active_storage/scopes.rbs +23 -0
- data/sig/rbs_activerecord/generator/associations.rbs +29 -0
- data/sig/rbs_activerecord/generator/attributes.rbs +28 -0
- data/sig/rbs_activerecord/generator/delegated_type/instance_methods.rbs +31 -0
- data/sig/rbs_activerecord/generator/delegated_type/scopes.rbs +26 -0
- data/sig/rbs_activerecord/generator/enum/base.rbs +19 -0
- data/sig/rbs_activerecord/generator/enum/instance_methods.rbs +26 -0
- data/sig/rbs_activerecord/generator/enum/scopes.rbs +26 -0
- data/sig/rbs_activerecord/generator/pluck_overloads.rbs +23 -0
- data/sig/rbs_activerecord/generator/scopes.rbs +27 -0
- data/sig/rbs_activerecord/generator/secure_password.rbs +22 -0
- data/sig/rbs_activerecord/generator.rbs +34 -0
- data/sig/rbs_activerecord/model.rbs +26 -0
- data/sig/rbs_activerecord/parser/evaluator.rbs +12 -0
- data/sig/rbs_activerecord/parser/visitor.rbs +27 -0
- data/sig/rbs_activerecord/parser.rbs +15 -0
- data/sig/rbs_activerecord/rake_task.rbs +19 -0
- data/sig/rbs_activerecord/utils.rbs +14 -0
- data/sig/rbs_activerecord/version.rbs +5 -0
- data/sig/rbs_activerecord.rbs +6 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b4083cdfd1907ddd9ba845bf3535ed264504753c6fe80c08ac723891a0c990aa
|
4
|
+
data.tar.gz: 8825d942ba46a8b35cd4aa3d57ef723aa7fbc51333d7fe30bdca1b143ed73693
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '07838fda26b2d56872fa91f7d3d9a89b2eb89c5c93591722547ca27af65d65ff72ae1703c8366d4c556559d18cbcc8668930c8c4e2bbb7c89aaaa3e1b7ec84a3'
|
7
|
+
data.tar.gz: 605694bc6458aa4f6076d89add3a162c66edceefd357f2402e989a093f898b853f1d363cf7d121a4e5e49befa4747bd99c2ae9a52f2a89502f25361ee1865ccf
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.1
|
3
|
+
|
4
|
+
Layout/LeadingCommentSpace:
|
5
|
+
AllowRBSInlineAnnotation: true
|
6
|
+
AllowSteepAnnotation: true
|
7
|
+
|
8
|
+
Metrics/BlockLength:
|
9
|
+
Exclude:
|
10
|
+
- "spec/**/*.rb"
|
11
|
+
|
12
|
+
Metrics/MethodLength:
|
13
|
+
Max: 30
|
14
|
+
|
15
|
+
Style/Documentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/StringLiterals:
|
19
|
+
EnforcedStyle: double_quotes
|
20
|
+
|
21
|
+
Style/StringLiteralsInInterpolation:
|
22
|
+
EnforcedStyle: double_quotes
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
10
|
+
identity and orientation.
|
11
|
+
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
13
|
+
diverse, inclusive, and healthy community.
|
14
|
+
|
15
|
+
## Our Standards
|
16
|
+
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
18
|
+
community include:
|
19
|
+
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
24
|
+
and learning from the experience
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
26
|
+
community
|
27
|
+
|
28
|
+
Examples of unacceptable behavior include:
|
29
|
+
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
31
|
+
any kind
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
33
|
+
* Public or private harassment
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
35
|
+
without their explicit permission
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
37
|
+
professional setting
|
38
|
+
|
39
|
+
## Enforcement Responsibilities
|
40
|
+
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
44
|
+
or harmful.
|
45
|
+
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
49
|
+
decisions when appropriate.
|
50
|
+
|
51
|
+
## Scope
|
52
|
+
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
54
|
+
an individual is officially representing the community in public spaces.
|
55
|
+
Examples of representing our community include using an official email address,
|
56
|
+
posting via an official social media account, or acting as an appointed
|
57
|
+
representative at an online or offline event.
|
58
|
+
|
59
|
+
## Enforcement
|
60
|
+
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
62
|
+
reported to the community leaders responsible for enforcement at
|
63
|
+
[INSERT CONTACT METHOD].
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
65
|
+
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
67
|
+
reporter of any incident.
|
68
|
+
|
69
|
+
## Enforcement Guidelines
|
70
|
+
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
73
|
+
|
74
|
+
### 1. Correction
|
75
|
+
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
77
|
+
unprofessional or unwelcome in the community.
|
78
|
+
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
82
|
+
|
83
|
+
### 2. Warning
|
84
|
+
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
86
|
+
actions.
|
87
|
+
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
93
|
+
ban.
|
94
|
+
|
95
|
+
### 3. Temporary Ban
|
96
|
+
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
98
|
+
sustained inappropriate behavior.
|
99
|
+
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
101
|
+
communication with the community for a specified period of time. No public or
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
104
|
+
Violating these terms may lead to a permanent ban.
|
105
|
+
|
106
|
+
### 4. Permanent Ban
|
107
|
+
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
111
|
+
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
113
|
+
community.
|
114
|
+
|
115
|
+
## Attribution
|
116
|
+
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
118
|
+
version 2.1, available at
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
120
|
+
|
121
|
+
Community Impact Guidelines were inspired by
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
123
|
+
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
127
|
+
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Takeshi KOMIYA
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# rbs_activerecord
|
2
|
+
|
3
|
+
rbs_activerecord is a RBSGenerator for models built with ActiveRecord.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add a new entry to your Gemfile and run `bundle install`:
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem 'rbs_activerecord', require: false
|
11
|
+
end
|
12
|
+
|
13
|
+
After the installation, please run rake task generator:
|
14
|
+
|
15
|
+
bundle exec rails g rbs_activerecord:install
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Run `rbs:activerecord:setup` task:
|
20
|
+
|
21
|
+
bundle exec rake rbs:activerecord:setup
|
22
|
+
|
23
|
+
Then rbs_activerecord will scan your code and generate RBS files into
|
24
|
+
`sig/activerecord` directory.
|
25
|
+
|
26
|
+
## Development
|
27
|
+
|
28
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also
|
29
|
+
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`.
|
32
|
+
To release a new version, update the version number in `version.rb`, and then put
|
33
|
+
a git tag (ex. `git tag v1.0.0`) and push it to the GitHub. Then GitHub Actions
|
34
|
+
will release a new package to [rubygems.org](https://rubygems.org) automatically.
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tk0miya/rbs_activerecord.
|
39
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are
|
40
|
+
expected to adhere to the [code of conduct](https://github.com/tk0miya/rbs_activerecord/blob/main/CODE_OF_CONDUCT.md).
|
41
|
+
|
42
|
+
## License
|
43
|
+
|
44
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
45
|
+
|
46
|
+
## Code of Conduct
|
47
|
+
|
48
|
+
Everyone interacting in the rbs_activerecord project's codebases, issue trackers is expected to
|
49
|
+
follow the [code of conduct](https://github.com/tk0miya/rbs_activerecord/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rubocop/rake_task"
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
task default: :rubocop
|
9
|
+
|
10
|
+
namespace :rbs do
|
11
|
+
task :generate do
|
12
|
+
sh "rbs-inline", "--opt-out", "--output=sig", "lib"
|
13
|
+
end
|
14
|
+
end
|
data/Steepfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module RbsActiverecord
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
def create_raketask
|
8
|
+
create_file "lib/tasks/rbs_activerecord.rake", <<~RUBY
|
9
|
+
# frozen_string_literal: true
|
10
|
+
|
11
|
+
begin
|
12
|
+
require "rbs_activerecord/rake_task"
|
13
|
+
|
14
|
+
RbsActiverecord::RakeTask.new
|
15
|
+
rescue LoadError
|
16
|
+
# failed to load rbs_activerecord. Skip to load rbs_activerecord tasks.
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module ActiveStorage
|
6
|
+
class InstanceMethods
|
7
|
+
attr_reader :model #: RbsActiverecord::Model
|
8
|
+
|
9
|
+
# @rbs model: RbsActiverecord::Model
|
10
|
+
def initialize(model) #: void
|
11
|
+
@model = model
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate #: String
|
15
|
+
<<~RBS.strip
|
16
|
+
module GeneratedActiveStorageInstanceMethods
|
17
|
+
#{attachments.map { |name, reflection| attachment(name, reflection) }.join("\n")}
|
18
|
+
end
|
19
|
+
RBS
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def attachments #: Hash[String, untyped]
|
25
|
+
if model.klass.respond_to?(:attachment_reflections)
|
26
|
+
model.klass.attachment_reflections # steep:ignore
|
27
|
+
else
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @rbs name: String
|
33
|
+
def attachment(name, reflection) #: String
|
34
|
+
case reflection.macro
|
35
|
+
when :has_one_attached
|
36
|
+
<<~RBS
|
37
|
+
def #{name}: () -> ::ActiveStorage::Attached::One
|
38
|
+
def #{name}=: (::ActionDispatch::Http::UploadedFile) -> ::ActionDispatch::Http::UploadedFile
|
39
|
+
| (::Rack::Test::UploadedFile) -> ::Rack::Test::UploadedFile
|
40
|
+
| (::ActiveStorage::Blob) -> ::ActiveStorage::Blob
|
41
|
+
| (::String) -> ::String
|
42
|
+
| ({ io: ::IO, filename: ::String, content_type: ::String? }) -> { io: ::IO, filename: ::String, content_type: ::String? }
|
43
|
+
| (nil) -> nil
|
44
|
+
RBS
|
45
|
+
when :has_many_attached
|
46
|
+
<<~RBS
|
47
|
+
def #{name}: () -> ::ActiveStroage::Attached::Many
|
48
|
+
def #{name}=: (untyped) -> untyped
|
49
|
+
RBS
|
50
|
+
else
|
51
|
+
raise "unknown macro: #{reflection.macro}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module ActiveStorage
|
6
|
+
class Scopes
|
7
|
+
attr_reader :model #: RbsActiverecord::Model
|
8
|
+
|
9
|
+
# @rbs model: RbsActiverecord::Model
|
10
|
+
def initialize(model) #: void
|
11
|
+
@model = model
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate #: String
|
15
|
+
<<~RBS.strip
|
16
|
+
module GeneratedActiveStorageScopeMethods[Relation]
|
17
|
+
#{attachments.map { |name, _| attachment(name) }.join("\n")}
|
18
|
+
end
|
19
|
+
RBS
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def attachments #: Hash[String, untyped]
|
25
|
+
if model.klass.respond_to?(:attachment_reflections)
|
26
|
+
model.klass.attachment_reflections # steep:ignore
|
27
|
+
else
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @rbs name: String
|
33
|
+
def attachment(name) #: String
|
34
|
+
"def with_attached_#{name}: () -> Relation"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
class Associations
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_reader :model #: RbsActiverecord::Model
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
def initialize(model) #: void
|
12
|
+
@model = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate #: String
|
16
|
+
<<~RBS.strip
|
17
|
+
module GeneratedAssociationMethods
|
18
|
+
#{has_many}
|
19
|
+
#{has_one}
|
20
|
+
#{belongs_to}
|
21
|
+
#{has_and_belongs_to_many}
|
22
|
+
end
|
23
|
+
RBS
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def has_many #: String # rubocop:disable Naming/PredicateName
|
29
|
+
model.reflect_on_all_associations(:has_many).map do |assoc|
|
30
|
+
assoc_name = assoc.name.to_s
|
31
|
+
klass_name = assoc.klass.name
|
32
|
+
collection = "#{klass_name}::ActiveRecord_Associations_CollectionProxy"
|
33
|
+
primary_key_type = primary_key_type_for(assoc.klass)
|
34
|
+
|
35
|
+
<<~RBS
|
36
|
+
def #{assoc_name}: () -> #{collection}
|
37
|
+
def #{assoc_name}=: (#{collection} | Array[::#{klass_name}]) -> (#{collection} | Array[::#{klass_name}])
|
38
|
+
def #{assoc_name.singularize}_ids: () -> Array[#{primary_key_type}]
|
39
|
+
def #{assoc_name.singularize}_ids=: (Array[#{primary_key_type}]) -> Array[#{primary_key_type}]
|
40
|
+
RBS
|
41
|
+
end.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_one #: String # rubocop:disable Naming/PredicateName
|
45
|
+
model.reflect_on_all_associations(:has_one).map do |assoc|
|
46
|
+
type = assoc.klass.name
|
47
|
+
optional = "#{type}?"
|
48
|
+
|
49
|
+
<<~RBS
|
50
|
+
def #{assoc.name}: () -> #{optional}
|
51
|
+
def #{assoc.name}=: (#{optional}) -> #{optional}
|
52
|
+
def build_#{assoc.name}: (?untyped) -> #{type}
|
53
|
+
def create_#{assoc.name}: (untyped) -> #{type}
|
54
|
+
def create_#{assoc.name}!: (untyped) -> #{type}
|
55
|
+
def reload_#{assoc.name}: () -> #{optional}
|
56
|
+
def reset_#{assoc.name}: () -> void
|
57
|
+
def #{assoc.name}_changed?: () -> bool
|
58
|
+
def #{assoc.name}_previously_changed?: () -> bool
|
59
|
+
RBS
|
60
|
+
end.join("\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
def belongs_to #: String # rubocop:disable Metrics/AbcSize
|
64
|
+
model.reflect_on_all_associations(:belongs_to).map do |assoc|
|
65
|
+
is_optional = assoc.options[:optional]
|
66
|
+
type = assoc.polymorphic? ? polymorphic_owner_types(assoc) : assoc.klass.name
|
67
|
+
optional = "#{type}?"
|
68
|
+
|
69
|
+
# @type var methods: Array[String]
|
70
|
+
methods = []
|
71
|
+
methods << "def #{assoc.name}: () -> #{is_optional ? optional : type}"
|
72
|
+
methods << "def #{assoc.name}=: (#{optional}) -> #{optional}"
|
73
|
+
unless assoc.polymorphic?
|
74
|
+
methods << "def build_#{assoc.name}: (untyped) -> #{type}"
|
75
|
+
methods << "def create_#{assoc.name}: (untyped) -> #{type}"
|
76
|
+
methods << "def create_#{assoc.name}!: (untyped) -> #{type}"
|
77
|
+
end
|
78
|
+
methods << "def reload_#{assoc.name}: () -> #{optional}"
|
79
|
+
methods << "def reset_#{assoc.name}: () -> void"
|
80
|
+
methods.join("\n")
|
81
|
+
end.join("\n")
|
82
|
+
end
|
83
|
+
|
84
|
+
# @rbs assoc: untyped
|
85
|
+
def polymorphic_owner_types(assoc) #: String # rubocop:disable Metrics/AbcSize
|
86
|
+
table_name = model.klass.name.to_s.tableize.to_sym
|
87
|
+
owners = ActiveRecord::Base.descendants.select do |klass|
|
88
|
+
klass.reflect_on_all_associations.any? { |a| a.name == table_name && a.options[:as] == assoc.name }
|
89
|
+
end
|
90
|
+
|
91
|
+
if owners.empty?
|
92
|
+
"untyped"
|
93
|
+
elsif owners.size == 1
|
94
|
+
owners.first.name
|
95
|
+
else
|
96
|
+
names = owners.map(&:name).sort.join(" | ")
|
97
|
+
"(#{names})"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def has_and_belongs_to_many #: String # rubocop:disable Naming/PredicateName
|
102
|
+
model.reflect_on_all_associations(:has_and_belongs_to_many).map do |assoc|
|
103
|
+
assoc_name = assoc.name.to_s
|
104
|
+
klass_name = assoc.klass.name
|
105
|
+
collection = "#{klass_name}::ActiveRecord_Associations_CollectionProxy"
|
106
|
+
primary_key_type = primary_key_type_for(assoc.klass)
|
107
|
+
|
108
|
+
<<~RBS
|
109
|
+
def #{assoc_name}: () -> #{collection}
|
110
|
+
def #{assoc_name}=: (#{collection} | Array[::#{klass_name}]) -> (#{collection} | Array[::#{klass_name}])
|
111
|
+
def #{assoc_name.singularize}_ids: () -> Array[#{primary_key_type}]
|
112
|
+
def #{assoc_name.singularize}_ids=: (Array[#{primary_key_type}]) -> Array[#{primary_key_type}]
|
113
|
+
RBS
|
114
|
+
end.join("\n")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
class Attributes
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_reader :model #: RbsActiverecord::Model
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
def initialize(model) #: void
|
12
|
+
@model = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate #: String
|
16
|
+
<<~RBS
|
17
|
+
module GeneratedAttributeMethods
|
18
|
+
#{model.columns.map { |c| column(c) }.join("\n")}
|
19
|
+
#{attributes.map { |name, type| attribute(name, type) }.join("\n")}
|
20
|
+
#{model.attribute_aliases.map { |from, to| alias_method(from, to) }.join("\n")}
|
21
|
+
end
|
22
|
+
RBS
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def column(col) #: String # rubocop:disable Metrics/AbcSize
|
28
|
+
type = sql_type_to_class(col.type)
|
29
|
+
optional = "#{type}?"
|
30
|
+
column_type = col.null ? optional : type
|
31
|
+
|
32
|
+
<<~RBS
|
33
|
+
def #{col.name}: () -> #{column_type}
|
34
|
+
def #{col.name}=: (#{column_type}) -> #{column_type}
|
35
|
+
def #{col.name}?: () -> bool
|
36
|
+
def #{col.name}_changed?: () -> bool
|
37
|
+
def #{col.name}_change: () -> [#{optional}, #{optional}]
|
38
|
+
def #{col.name}_will_change!: () -> void
|
39
|
+
def #{col.name}_was: () -> #{optional}
|
40
|
+
def #{col.name}_previously_changed?: () -> bool
|
41
|
+
def #{col.name}_previous_change: () -> ::Array[#{optional}]?
|
42
|
+
def #{col.name}_previously_was: () -> #{optional}
|
43
|
+
def #{col.name}_before_last_save: () -> #{optional}
|
44
|
+
def #{col.name}_change_to_be_saved: () -> ::Array[#{optional}]?
|
45
|
+
def #{col.name}_in_database: () -> #{optional}
|
46
|
+
def saved_change_to_#{col.name}: () -> ::Array[#{optional}]?
|
47
|
+
def saved_change_to_#{col.name}?: () -> bool
|
48
|
+
def will_save_change_to_#{col.name}?: () -> bool
|
49
|
+
def restore_#{col.name}!: () -> void
|
50
|
+
def clear_#{col.name}_change: () -> void
|
51
|
+
RBS
|
52
|
+
end
|
53
|
+
|
54
|
+
def attributes #: Hash[String, untyped]
|
55
|
+
model.attribute_types.filter_map do |name, type|
|
56
|
+
[name, type] if model.columns.none? { |col| col.name == name }
|
57
|
+
end.to_h
|
58
|
+
end
|
59
|
+
|
60
|
+
# @rbs name: String
|
61
|
+
# @rbs type: untyped
|
62
|
+
def attribute(name, type) #: String
|
63
|
+
column_type = sql_type_to_class(type.type)
|
64
|
+
|
65
|
+
<<~RBS
|
66
|
+
def #{name}: () -> #{column_type}
|
67
|
+
def #{name}=: (#{column_type}) -> #{column_type}
|
68
|
+
RBS
|
69
|
+
end
|
70
|
+
|
71
|
+
def alias_method(from, to) #: String
|
72
|
+
<<~RBS
|
73
|
+
alias #{from} #{to}
|
74
|
+
alias #{from}= #{to}=
|
75
|
+
alias #{from}? #{to}?
|
76
|
+
alias #{from}_changed? #{to}_changed?
|
77
|
+
alias #{from}_change #{to}_change
|
78
|
+
alias #{from}_will_change! #{to}_will_change!
|
79
|
+
alias #{from}_was #{to}_was
|
80
|
+
alias #{from}_previously_changed? #{to}_previously_changed?
|
81
|
+
alias #{from}_previous_change #{to}_previous_change
|
82
|
+
alias #{from}_previously_was #{to}_previously_was
|
83
|
+
alias #{from}_before_last_save #{to}_before_last_save
|
84
|
+
alias #{from}_change_to_be_saved #{to}_change_to_be_saved
|
85
|
+
alias #{from}_in_database #{to}_in_database
|
86
|
+
alias saved_change_to_#{from} saved_change_to_#{to}
|
87
|
+
alias saved_change_to_#{from}? saved_change_to_#{to}?
|
88
|
+
alias will_save_change_to_#{from}? will_save_change_to_#{to}?
|
89
|
+
alias restore_#{from}! restore_#{to}!
|
90
|
+
alias clear_#{from}_change clear_#{to}_change
|
91
|
+
RBS
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module DelegatedType
|
6
|
+
class InstanceMethods
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
attr_reader :model #: RbsActiverecord::Model
|
10
|
+
attr_reader :declarations #: Array[Prism::CallNode]
|
11
|
+
|
12
|
+
# @rbs model: RbsActiverecord::Model
|
13
|
+
# @rbs declarations: Hash[String, Array[Prism::CallNode]]
|
14
|
+
def initialize(model, declarations) #: void
|
15
|
+
@model = model
|
16
|
+
@declarations = declarations.fetch(model.klass.name.to_s, [])
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate #: String
|
20
|
+
<<~RBS.strip
|
21
|
+
module GeneratedDelegatedTypeInstanceMethods
|
22
|
+
#{delegated_types.map { |node| delegated_type(node) }.join("\n")}
|
23
|
+
end
|
24
|
+
RBS
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def delegated_types #: Array[Prism::CallNode]
|
30
|
+
declarations.select { |node| node.name == :delegated_type }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @rbs node: Prism::CallNode
|
34
|
+
def delegated_type(node) #: String
|
35
|
+
arguments = node.arguments&.arguments || []
|
36
|
+
name, options = arguments.map { |arg| Parser.eval_node(arg) } #: [String?, Hash[Symbol, untyped]]
|
37
|
+
return "" unless name
|
38
|
+
|
39
|
+
types = options[:types]
|
40
|
+
role_methods = <<~RBS
|
41
|
+
def #{name}_class: () -> (#{types.join(" | ")})
|
42
|
+
def #{name}_name: () -> String
|
43
|
+
RBS
|
44
|
+
|
45
|
+
type_methods = types.map do |type|
|
46
|
+
<<~RBS
|
47
|
+
def #{type.underscore}?: () -> bool
|
48
|
+
def #{type.underscore}: () -> #{type}?
|
49
|
+
def #{type.underscore}_id: () -> #{primary_key_type_for(type)}?
|
50
|
+
RBS
|
51
|
+
end.join("\n")
|
52
|
+
|
53
|
+
role_methods + type_methods
|
54
|
+
end
|
55
|
+
|
56
|
+
# @rbs klass_name: String
|
57
|
+
def primary_key_type_for(klass_name) #: String
|
58
|
+
klass = Object.const_get(klass_name)
|
59
|
+
super(klass)
|
60
|
+
rescue NameError
|
61
|
+
"::Integer"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|