orthoses-rails 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9681e80e9a106b8d984c304a0f26777deb5e2077aba6f8a0321f52976e24bcc
4
- data.tar.gz: c1f248d0e236bc72491e507da185317d55d607cebf4d6364209cd1601cacb6aa
3
+ metadata.gz: a9a2d180cb8faa295dc3043ea66a379ee71939436075104c0f85b63d6aede665
4
+ data.tar.gz: e02fa19ea12dfd296936673d67ecc6457b0e16101c18d4851747e0b895dace96
5
5
  SHA512:
6
- metadata.gz: 8ff02244b5844c28186021d4379818a5a0b56ffcb693425f500e3145ff36da721a278c972a6a324e3b3b26bcb3ce9652c98f9fb3c15549fd51924371362d37fb
7
- data.tar.gz: 284d60f37efb290cf6e5230dd10cda55066278e89e4b81866896793c535b4864edc7cb13c389d0c98a861c34630fba09df3bb04b1035291f7c4cc46666d22b9b
6
+ metadata.gz: 9e97ed8964e301618db94df7c0d410310ea3bd9605f66c9d7d0431dc71546e0faaff24f918ce05a58510250f7563934a9adb3977662d3526db0198016801ba77
7
+ data.tar.gz: 5d43746f2bcb1ae068902a66a0da54a852bb173fcdd85ff5d9ce0f07e2051cbbc31e7b1a2c8e0469b28e2ca6ca1661edb76a1665e6d7a3356c6bedb9de97cf00
@@ -17,6 +17,13 @@ namespace :orthoses do
17
17
  # Complement missing const name.
18
18
  use Orthoses::MissingName
19
19
 
20
+ # You can use other publicly available middleware.
21
+ # `Orthoses::YARD` is available at https://github.com/ksss/orthoses-yard.
22
+ # By using this middleware, you can add the capability
23
+ # to generate type information from YARD documentation.
24
+ # use Orthoses::YARD,
25
+ # parse: ['{app/lib}/**/*.rb']
26
+
20
27
  # You can load hand written RBS.
21
28
  # use Orthoses::LoadRBS,
22
29
  # paths: Dir.glob(Rails.root / "sig/hand-written/**/*.rbs")
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ module ActionMailer
5
+ class Base
6
+ def initialize(loader)
7
+ @loader = loader
8
+ end
9
+
10
+ def call
11
+ @loader.call.tap do |store|
12
+ ::ActionMailer::Base.descendants.each do |mailer_class|
13
+ base_name = Utils.module_name(mailer_class) or next
14
+ content = store[base_name]
15
+ mailer_class.action_methods.each do |action_method|
16
+ method_object = mailer_class.instance_method(action_method)
17
+ parameters = method_parameters_to_rbs_method_type(method_object).to_s.gsub(' -> untyped', '')
18
+ content << "def self.#{action_method}: #{parameters} -> ::ActionMailer::MessageDelivery"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def method_parameters_to_rbs_method_type(method_object)
27
+ required_positionals = []
28
+ optional_positionals = []
29
+ rest_positionals = nil
30
+ trailing_positionals = []
31
+ required_keywords = {}
32
+ optional_keywords = {}
33
+ rest_keywords = nil
34
+ requireds = required_positionals
35
+ block = nil
36
+ untyped = RBS::Types::Bases::Any.new(location: nil)
37
+
38
+ method_object.parameters.each do |kind, name|
39
+ case kind
40
+ when :req
41
+ requireds << ::RBS::Types::Function::Param.new(name: name, type: untyped)
42
+ when :opt
43
+ requireds = trailing_positionals
44
+ optional_positionals << ::RBS::Types::Function::Param.new(name: name, type: untyped)
45
+ when :rest
46
+ requireds = trailing_positionals
47
+ name = nil if name == :*
48
+ rest_positionals = ::RBS::Types::Function::Param.new(name: name, type: untyped)
49
+ when :keyreq
50
+ required_keywords[name] = ::RBS::Types::Function::Param.new(name: nil, type: untyped)
51
+ when :key
52
+ optional_keywords[name] = ::RBS::Types::Function::Param.new(name: nil, type: untyped)
53
+ when :keyrest
54
+ rest_keywords = ::RBS::Types::Function::Param.new(name: name, type: untyped)
55
+ when :block
56
+ block = RBS::Types::Block.new(
57
+ type: RBS::Types::Function.empty(untyped).update(rest_positionals: RBS::Types::Function::Param.new(name: nil, type: untyped)),
58
+ required: true,
59
+ self_type: nil
60
+ )
61
+ else
62
+ raise "bug"
63
+ end
64
+ end
65
+
66
+ function = ::RBS::Types::Function.new(
67
+ required_positionals: required_positionals,
68
+ optional_positionals: optional_positionals,
69
+ rest_positionals: rest_positionals,
70
+ trailing_positionals: trailing_positionals,
71
+ required_keywords: required_keywords,
72
+ optional_keywords: optional_keywords,
73
+ rest_keywords: rest_keywords,
74
+ return_type: untyped,
75
+ )
76
+ ::RBS::MethodType.new(
77
+ location: nil,
78
+ type_params: [],
79
+ type: function,
80
+ block: block
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'action_mailer/base'
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ module ActiveModel
5
+ # < 7.0
6
+ # def attribute(name, type = Type::Value.new, **options)
7
+ # >= 7.0
8
+ # def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
9
+ class Attributes
10
+ DEFAULT_TYPES = {
11
+ big_integer: '::Integer',
12
+ binary: '::String',
13
+ boolean: 'bool',
14
+ date: '::Date',
15
+ datetime: '::Time',
16
+ decimal: '::BigDecimal',
17
+ float: '::Float',
18
+ immutable_string: '::String',
19
+ integer: '::Integer',
20
+ string: '::String',
21
+ time: '::Time'
22
+ }
23
+
24
+ def initialize(loader)
25
+ @loader = loader
26
+ end
27
+
28
+ def call
29
+ attribute = CallTracer::Lazy.new
30
+ store = attribute.trace('ActiveModel::Attributes::ClassMethods#attribute') do
31
+ @loader.call
32
+ end
33
+ attribute.captures.each do |capture|
34
+ receiver_name = Utils.module_name(capture.method.receiver) or next
35
+ name = capture.argument[:name]
36
+ active_model_version = Gem::Version.new(::ActiveModel::VERSION::STRING)
37
+ cast_type =
38
+ if active_model_version >= Gem::Version.new('7.1')
39
+ # https://github.com/rails/rails/commit/608cbfae36b125d7962b7ed9083c9e9e6ce70b88
40
+ capture.argument[:*].first
41
+ elsif active_model_version >= Gem::Version.new('7.0')
42
+ capture.argument[:cast_type]
43
+ else
44
+ capture.argument[:type]
45
+ end
46
+
47
+ return_type = DEFAULT_TYPES[cast_type] || 'untyped'
48
+
49
+ generated_attribute_methods = "#{receiver_name}::ActiveModelGeneratedAttributeMethods"
50
+ c = store[generated_attribute_methods]
51
+ c.header = "module #{generated_attribute_methods}"
52
+ c << "def #{name}: () -> #{return_type}?"
53
+ c << "def #{name}=: (untyped) -> untyped"
54
+
55
+ unless store[receiver_name].body.include?("include #{generated_attribute_methods}")
56
+ store[receiver_name] << "include #{generated_attribute_methods}"
57
+ end
58
+ end
59
+
60
+ store
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'active_model/attributes'
3
4
  require_relative 'active_model/has_secure_password'
@@ -67,15 +67,35 @@ module Orthoses
67
67
 
68
68
  return_type_param = Hash === values ? "[String, String]" : "[String, Integer]"
69
69
  store[base_name] << "def self.#{name.pluralize}: () -> ActiveSupport::HashWithIndifferentAccess#{return_type_param}"
70
+ enum_methods_content = store["#{base_name}::ActiveRecord_Enum_EnumMethods"]
71
+ enum_methods_content.header = "module #{base_name}::ActiveRecord_Enum_EnumMethods"
70
72
 
71
- pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
73
+ pairs = (values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index).to_h
72
74
  pairs.each do |label, value|
73
75
  value_method_name = "#{prefix}#{label}#{suffix}"
74
- enum_methods_content = store["#{base_name}::ActiveRecord_Enum_EnumMethods"]
75
- enum_methods_content.header = "module #{base_name}::ActiveRecord_Enum_EnumMethods"
76
76
  enum_methods_content << "def #{value_method_name}?: () -> bool"
77
77
  enum_methods_content << "def #{value_method_name}!: () -> bool"
78
+
79
+ value_method_alias = "#{prefix}#{label.to_s.gsub(/[\W&&[:ascii:]]+/, "_")}#{suffix}"
80
+ if value_method_alias != value_method_name
81
+ enum_methods_content << "def #{value_method_alias}?: () -> bool"
82
+ enum_methods_content << "def #{value_method_alias}!: () -> bool"
83
+ end
84
+ end
85
+
86
+ # Expressing type casting.
87
+ store[base_name] << "def #{name}: () -> (#{pairs.keys.map { |k| "\"#{k}\"" }.join(" | ")})"
88
+ symbol_pattern = if pairs.keys.any? { |key| key.match?(/[^a-zA-Z_]/) }
89
+ "(Symbol) -> void"
90
+ else
91
+ "(#{pairs.keys.map{|key|key.to_sym.inspect}.join(" | ")}) -> void"
78
92
  end
93
+ overloads = [
94
+ symbol_pattern,
95
+ "(#{pairs.keys.map{|key|key.to_s.inspect}.join(" | ")}) -> void",
96
+ "(#{pairs.values.map(&:inspect).join(" | ")}) -> void",
97
+ ]
98
+ store[base_name] << "def #{name}=: #{overloads.join(" | ")}"
79
99
  end
80
100
  end
81
101
  end
@@ -4,8 +4,6 @@ module Orthoses
4
4
  module ActiveRecord
5
5
  class GeneratedAttributeMethods
6
6
  TARGET_TYPE_MAP = {
7
- "attribute" => "() -> %<type>s",
8
- "attribute=" => "(%<type>s) -> %<type>s",
9
7
  "attribute?" => "() -> bool",
10
8
  "attribute_before_last_save" => "() -> %<opt>s",
11
9
  "attribute_before_type_cast" => "() -> %<type>s",
@@ -50,13 +48,14 @@ module Orthoses
50
48
  opt = "#{req}?"
51
49
  type = col.null ? opt : req
52
50
 
53
- ::ActiveRecord::Base.attribute_method_matchers.each do |matcher|
54
- tmpl = TARGET_TYPE_MAP[matcher.target]
51
+ lines << "attr_accessor #{name}: #{type}"
52
+ attribute_method_patterns(::ActiveRecord::Base).each do |matcher|
53
+ tmpl = TARGET_TYPE_MAP[matcher.proxy_target] or next
55
54
  lines << "def #{matcher.method_name(name)}: #{tmpl % {type: type, opt: opt}}"
56
55
  end
57
56
  end
58
57
  klass.attribute_aliases.each do |alias_name, column_name|
59
- ::ActiveRecord::Base.attribute_method_matchers.each do |matcher|
58
+ attribute_method_patterns(::ActiveRecord::Base).each do |matcher|
60
59
  lines << "alias #{matcher.method_name(alias_name)} #{matcher.method_name(column_name)}"
61
60
  end
62
61
  end
@@ -69,6 +68,20 @@ module Orthoses
69
68
  end
70
69
  end
71
70
  end
71
+
72
+ private
73
+
74
+ def attribute_method_patterns(const)
75
+ if const.respond_to?(:attribute_method_patterns)
76
+ const.attribute_method_patterns
77
+ else
78
+ const.attribute_method_matchers.each do |matcher|
79
+ def matcher.proxy_target
80
+ target
81
+ end
82
+ end
83
+ end
84
+ end
72
85
  end
73
86
  end
74
87
  end
@@ -19,7 +19,16 @@ module Orthoses
19
19
  class_specific_proxy = "#{model_name}::ActiveRecord_Associations_CollectionProxy"
20
20
  class_specific_generated_relation_methods = "#{model_name}::GeneratedRelationMethods"
21
21
 
22
+ # Expressing delegation.
22
23
  store[class_specific_generated_relation_methods].tap do |c|
24
+ klass.singleton_methods(false).each do |singleton_method|
25
+ c << "def #{singleton_method}: (*untyped, **untyped) -> untyped"
26
+ end
27
+ (klass.singleton_class.included_modules - ::ActiveRecord::Relation.included_modules).each do |mod|
28
+ mname = Utils.module_name(mod) or next
29
+ store[mname].header = "module #{mname}"
30
+ c << "include #{mname}"
31
+ end
23
32
  end
24
33
 
25
34
  store[class_specific_relation].tap do |c|
@@ -20,7 +20,7 @@ module Orthoses
20
20
  name = capture.argument[:name]
21
21
  body = capture.argument[:body]
22
22
 
23
- definition = "#{name}: #{parameters_to_type(body.parameters)} -> #{base_name}::ActiveRecord_Relation"
23
+ definition = "#{name}: #{parameters_to_type(parameters(body))} -> #{base_name}::ActiveRecord_Relation"
24
24
  store[base_name] << "def self.#{definition}"
25
25
  store["#{base_name}::GeneratedRelationMethods"].header = "module #{base_name}::GeneratedRelationMethods"
26
26
  store["#{base_name}::GeneratedRelationMethods"] << "def #{definition}"
@@ -58,6 +58,14 @@ module Orthoses
58
58
  end
59
59
  "(#{res.join(", ")})#{block}"
60
60
  end
61
+
62
+ def parameters(body)
63
+ if body.respond_to?(:to_proc)
64
+ body.to_proc.parameters
65
+ else
66
+ body.method(:call).parameters
67
+ end
68
+ end
61
69
  end
62
70
  end
63
71
  end
@@ -36,7 +36,7 @@ module Orthoses
36
36
  def members_prototype_of(mod_name)
37
37
  mod = Object.const_get(mod_name)
38
38
  runtime = ::RBS::Prototype::Runtime.new(patterns: nil, env: nil, merge: false)
39
- type_name = runtime.to_type_name(mod_name)
39
+ type_name = TypeName(mod_name)
40
40
  [].tap do |members|
41
41
  runtime.generate_methods(mod, type_name, members)
42
42
  end
@@ -17,6 +17,9 @@ module Orthoses
17
17
  def call
18
18
  loader = @loader
19
19
  Orthoses::Builder.new do
20
+ use Orthoses::ActionMailer::Base
21
+
22
+ use Orthoses::ActiveModel::Attributes
20
23
  use Orthoses::ActiveModel::HasSecurePassword
21
24
 
22
25
  use Orthoses::ActiveRecord::BelongsTo
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Orthoses
4
4
  module Rails
5
- VERSION = "1.2.0"
5
+ VERSION = "1.4.0"
6
6
  end
7
7
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'orthoses'
4
4
 
5
+ require_relative 'action_mailer'
5
6
  require_relative 'active_model'
6
7
  require_relative 'active_record'
7
8
  require_relative 'active_storage'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orthoses-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ksss
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-02 00:00:00.000000000 Z
11
+ date: 2023-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: orthoses
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.11'
19
+ version: '1.13'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.11'
26
+ version: '1.13'
27
27
  description: Orthoses middleware collection for Ruby on Rails
28
28
  email:
29
29
  - co000ri@gmail.com
@@ -37,7 +37,10 @@ files:
37
37
  - lib/generators/orthoses/rails/install_generator.rb
38
38
  - lib/generators/orthoses/rails/templates/rails.rake
39
39
  - lib/orthoses-rails.rb
40
+ - lib/orthoses/action_mailer.rb
41
+ - lib/orthoses/action_mailer/base.rb
40
42
  - lib/orthoses/active_model.rb
43
+ - lib/orthoses/active_model/attributes.rb
41
44
  - lib/orthoses/active_model/has_secure_password.rb
42
45
  - lib/orthoses/active_record.rb
43
46
  - lib/orthoses/active_record/belongs_to.rb