activerecord-ejection_seat 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -8
  3. data/.ruby-version +1 -0
  4. data/.tool-versions +1 -0
  5. data/Gemfile +2 -2
  6. data/Gemfile.lock +42 -55
  7. data/README.md +16 -6
  8. data/Rakefile +6 -6
  9. data/activerecord-ejection_seat.gemspec +40 -0
  10. data/lib/activerecord-ejection_seat/attributes_builder.rb +42 -0
  11. data/lib/activerecord-ejection_seat/ejectable.rb +7 -10
  12. data/lib/activerecord-ejection_seat/props_builder.rb +35 -23
  13. data/lib/activerecord-ejection_seat/version.rb +2 -2
  14. data/lib/activerecord-ejection_seat.rb +6 -3
  15. data/sorbet/rbi/gems/activemodel@7.0.4.1.rbi +6022 -0
  16. data/sorbet/rbi/gems/activerecord@7.0.4.1.rbi +37791 -0
  17. data/sorbet/rbi/gems/activesupport@7.0.4.1.rbi +18127 -0
  18. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11626 -2
  19. data/sorbet/rbi/gems/minitest@5.17.0.rbi +2319 -0
  20. data/sorbet/rbi/gems/{parser@3.1.3.0.rbi → parser@3.2.0.0.rbi} +907 -821
  21. data/sorbet/rbi/gems/{prettier_print@1.1.0.rbi → prettier_print@1.2.0.rbi} +0 -0
  22. data/sorbet/rbi/gems/{rubocop-ast@1.24.0.rbi → rubocop-ast@1.24.1.rbi} +702 -458
  23. data/sorbet/rbi/gems/rubocop-minitest@0.26.1.rbi +2320 -0
  24. data/sorbet/rbi/gems/{rubocop@1.41.0.rbi → rubocop@1.43.0.rbi} +2743 -625
  25. data/sorbet/rbi/gems/{ruby-lsp@0.3.7.rbi → ruby-lsp@0.3.8.rbi} +2 -1
  26. data/sorbet/rbi/gems/{sqlite3@1.5.4.rbi → sqlite3@1.6.0.rbi} +73 -0
  27. data/sorbet/rbi/gems/{syntax_tree@4.3.0.rbi → syntax_tree@5.2.0.rbi} +0 -0
  28. data/sorbet/rbi/gems/{tapioca@0.10.4.rbi → tapioca@0.10.5.rbi} +279 -96
  29. data/sorbet/rbi/gems/{unicode-display_width@2.3.0.rbi → unicode-display_width@2.4.2.rbi} +26 -7
  30. data/sorbet/rbi/gems/{unparser@0.6.5.rbi → unparser@0.6.7.rbi} +323 -64
  31. data/sorbet/rbi/gems/{yard-sorbet@0.7.0.rbi → yard-sorbet@0.8.0.rbi} +91 -41
  32. data/sorbet/rbi/shims/location.rbi +13 -0
  33. data/sorbet/rbi/shims/post.rbi +16 -0
  34. data/sorbet/rbi/shims/user.rbi +19 -0
  35. data/sorbet/rbi/todo.rbi +0 -4
  36. data/sorbet/tapioca/require.rb +16 -2
  37. metadata +29 -27
  38. data/sorbet/rbi/gems/activemodel@7.0.4.rbi +0 -8
  39. data/sorbet/rbi/gems/activerecord@7.0.4.rbi +0 -11
  40. data/sorbet/rbi/gems/activesupport@7.0.4.rbi +0 -93
  41. data/sorbet/rbi/gems/minitest@5.16.3.rbi +0 -8
  42. data/sorbet/rbi/gems/rspec-core@3.12.0.rbi +0 -10588
  43. data/sorbet/rbi/gems/rspec-expectations@3.12.1.rbi +0 -7817
  44. data/sorbet/rbi/gems/rspec-mocks@3.12.1.rbi +0 -4994
  45. data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +0 -1477
  46. data/sorbet/rbi/gems/rspec@3.12.0.rbi +0 -10
  47. data/sorbet/rbi/gems/rubocop-rspec@2.16.0.rbi +0 -7650
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec011864f722a24abd09c60e4dbfd8c53e1a41a2922ef6827a527f3992d5b941
4
- data.tar.gz: ea35286dccfb37c99279b6ffe6403fd20ee72c8a488103bdaa4386e7f6c8b205
3
+ metadata.gz: f8cb74f8d55bb740891ac604fbb974cbbc78ce7e01bd67eb739a01cd4cdae756
4
+ data.tar.gz: c14a4d5a2382acda29d63c3a77bbf0e4080eff154e3b6cccfb4b91aa13b120e1
5
5
  SHA512:
6
- metadata.gz: a2dfb9277f769f3e9296a5388459e02acfa98ae0697009e3e8587dbaed774d45ec059652a7ffdf61c5a2a36c62b309e32fa10b834f20a8e84d145fc3c6f78dda
7
- data.tar.gz: b39ccda5b3aa68a6d63f9be7959f70daf7a8e262f67d0b4afbf9bd250c1f1cb42f25d28592b5223ccd46857548a2ce7936d061bc9f0b62030bbe870b03e7d5c3
6
+ metadata.gz: e4f736acf5cf15bab547d35f2cf4c21963a6a188029df0c23f59dbbbc900ef7474f0de67013ccac7afa853d832f5fb839e0503019b37a90a2508cab7ce0def55
7
+ data.tar.gz: 787f28eaa9428fbe03e6d48be126d591af896da66e4a360c250e9d9b70ae9d3a03505f66134b53e6ba8be927cbced95b52fd12d2e8a288e582a9189618d307e6
data/.rubocop.yml CHANGED
@@ -1,11 +1,20 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
1
5
  require:
6
+ - rubocop-minitest
2
7
  - rubocop-rake
3
- - rubocop-rspec
4
8
  - rubocop-sorbet
5
9
 
6
10
  AllCops:
7
11
  NewCops: enable
8
- TargetRubyVersion: 2.7
12
+ TargetRubyVersion: 3.0
13
+ Exclude:
14
+ - sorbet/**/*.rbi
15
+
16
+ Style/AccessorGrouping:
17
+ Enabled: false
9
18
 
10
19
  Style/StringLiterals:
11
20
  Enabled: true
@@ -17,9 +26,3 @@ Style/StringLiteralsInInterpolation:
17
26
 
18
27
  Layout/LineLength:
19
28
  Max: 120
20
-
21
- RSpec/FilePath:
22
- Enabled: false
23
-
24
- RSpec/MultipleExpectations:
25
- Max: 3
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.0
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.2.0
data/Gemfile CHANGED
@@ -8,8 +8,8 @@ gemspec
8
8
  group :development do
9
9
  gem "rake"
10
10
  gem "rubocop"
11
+ gem "rubocop-minitest"
11
12
  gem "rubocop-rake"
12
- gem "rubocop-rspec"
13
13
  gem "rubocop-sorbet"
14
14
  gem "sorbet"
15
15
  gem "spoom", require: false
@@ -19,7 +19,7 @@ group :development do
19
19
  end
20
20
 
21
21
  group :development, :test do
22
- gem "rspec"
22
+ gem "minitest"
23
23
  gem "sorbet-runtime"
24
24
  gem "sorbet-struct-comparable"
25
25
  gem "sqlite3"
data/Gemfile.lock CHANGED
@@ -1,26 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activerecord-ejection_seat (0.1.0)
5
- activerecord (>= 5.2)
4
+ activerecord-ejection_seat (0.3.0)
5
+ activerecord (>= 6.0)
6
6
  sorbet-runtime
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (7.0.4)
12
- activesupport (= 7.0.4)
13
- activerecord (7.0.4)
14
- activemodel (= 7.0.4)
15
- activesupport (= 7.0.4)
16
- activesupport (7.0.4)
11
+ activemodel (7.0.4.1)
12
+ activesupport (= 7.0.4.1)
13
+ activerecord (7.0.4.1)
14
+ activemodel (= 7.0.4.1)
15
+ activesupport (= 7.0.4.1)
16
+ activesupport (7.0.4.1)
17
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
18
  i18n (>= 1.6, < 2)
19
19
  minitest (>= 5.1)
20
20
  tzinfo (~> 2.0)
21
21
  ast (2.4.2)
22
22
  concurrent-ruby (1.1.10)
23
- debug (1.7.0)
23
+ debug (1.7.1)
24
24
  irb (>= 1.5.0)
25
25
  reline (>= 0.3.1)
26
26
  diff-lcs (1.5.0)
@@ -31,12 +31,12 @@ GEM
31
31
  reline (>= 0.3.0)
32
32
  json (2.6.3)
33
33
  language_server-protocol (3.17.0.2)
34
- minitest (5.16.3)
34
+ minitest (5.17.0)
35
35
  netrc (0.11.0)
36
36
  parallel (1.22.1)
37
- parser (3.1.3.0)
37
+ parser (3.2.0.0)
38
38
  ast (~> 2.4.1)
39
- prettier_print (1.1.0)
39
+ prettier_print (1.2.0)
40
40
  rainbow (3.1.1)
41
41
  rake (13.0.6)
42
42
  rbi (0.0.16)
@@ -48,62 +48,49 @@ GEM
48
48
  reline (0.3.2)
49
49
  io-console (~> 0.5)
50
50
  rexml (3.2.5)
51
- rspec (3.12.0)
52
- rspec-core (~> 3.12.0)
53
- rspec-expectations (~> 3.12.0)
54
- rspec-mocks (~> 3.12.0)
55
- rspec-core (3.12.0)
56
- rspec-support (~> 3.12.0)
57
- rspec-expectations (3.12.1)
58
- diff-lcs (>= 1.2.0, < 2.0)
59
- rspec-support (~> 3.12.0)
60
- rspec-mocks (3.12.1)
61
- diff-lcs (>= 1.2.0, < 2.0)
62
- rspec-support (~> 3.12.0)
63
- rspec-support (3.12.0)
64
- rubocop (1.41.0)
51
+ rubocop (1.43.0)
65
52
  json (~> 2.3)
66
53
  parallel (~> 1.10)
67
- parser (>= 3.1.2.1)
54
+ parser (>= 3.2.0.0)
68
55
  rainbow (>= 2.2.2, < 4.0)
69
56
  regexp_parser (>= 1.8, < 3.0)
70
57
  rexml (>= 3.2.5, < 4.0)
71
- rubocop-ast (>= 1.23.0, < 2.0)
58
+ rubocop-ast (>= 1.24.1, < 2.0)
72
59
  ruby-progressbar (~> 1.7)
73
- unicode-display_width (>= 1.4.0, < 3.0)
74
- rubocop-ast (1.24.0)
60
+ unicode-display_width (>= 2.4.0, < 3.0)
61
+ rubocop-ast (1.24.1)
75
62
  parser (>= 3.1.1.0)
63
+ rubocop-minitest (0.26.1)
64
+ rubocop (>= 0.90, < 2.0)
76
65
  rubocop-rake (0.6.0)
77
66
  rubocop (~> 1.0)
78
- rubocop-rspec (2.16.0)
79
- rubocop (~> 1.33)
80
67
  rubocop-sorbet (0.6.11)
81
68
  rubocop (>= 0.90.0)
82
- ruby-lsp (0.3.7)
69
+ ruby-lsp (0.3.8)
83
70
  language_server-protocol (~> 3.17.0)
84
71
  sorbet-runtime
85
- syntax_tree (>= 4.0.2, < 5.0.0)
72
+ syntax_tree (>= 5.0.0, < 6)
86
73
  ruby-progressbar (1.11.0)
87
- sorbet (0.5.10597)
88
- sorbet-static (= 0.5.10597)
89
- sorbet-runtime (0.5.10597)
90
- sorbet-static (0.5.10597-universal-darwin-21)
91
- sorbet-static (0.5.10597-universal-darwin-22)
92
- sorbet-static (0.5.10597-x86_64-linux)
93
- sorbet-static-and-runtime (0.5.10597)
94
- sorbet (= 0.5.10597)
95
- sorbet-runtime (= 0.5.10597)
74
+ sorbet (0.5.10626)
75
+ sorbet-static (= 0.5.10626)
76
+ sorbet-runtime (0.5.10626)
77
+ sorbet-static (0.5.10626-universal-darwin-21)
78
+ sorbet-static (0.5.10626-universal-darwin-22)
79
+ sorbet-static (0.5.10626-x86_64-linux)
80
+ sorbet-static-and-runtime (0.5.10626)
81
+ sorbet (= 0.5.10626)
82
+ sorbet-runtime (= 0.5.10626)
96
83
  sorbet-struct-comparable (1.3.0)
97
84
  sorbet-runtime (>= 0.5)
98
85
  spoom (1.1.15)
99
86
  sorbet (>= 0.5.10187)
100
87
  sorbet-runtime (>= 0.5.9204)
101
88
  thor (>= 0.19.2)
102
- sqlite3 (1.5.4-arm64-darwin)
103
- sqlite3 (1.5.4-x86_64-linux)
104
- syntax_tree (4.3.0)
105
- prettier_print (>= 1.0.2)
106
- tapioca (0.10.4)
89
+ sqlite3 (1.6.0-arm64-darwin)
90
+ sqlite3 (1.6.0-x86_64-linux)
91
+ syntax_tree (5.2.0)
92
+ prettier_print (>= 1.2.0)
93
+ tapioca (0.10.5)
107
94
  bundler (>= 1.17.3)
108
95
  netrc (>= 0.11.0)
109
96
  parallel (>= 1.21.0)
@@ -115,14 +102,14 @@ GEM
115
102
  thor (1.2.1)
116
103
  tzinfo (2.0.5)
117
104
  concurrent-ruby (~> 1.0)
118
- unicode-display_width (2.3.0)
119
- unparser (0.6.5)
105
+ unicode-display_width (2.4.2)
106
+ unparser (0.6.7)
120
107
  diff-lcs (~> 1.3)
121
- parser (>= 3.1.0)
108
+ parser (>= 3.2.0)
122
109
  webrick (1.7.0)
123
110
  yard (0.9.28)
124
111
  webrick (~> 1.7.0)
125
- yard-sorbet (0.7.0)
112
+ yard-sorbet (0.8.0)
126
113
  sorbet-runtime (>= 0.5)
127
114
  yard (>= 0.9)
128
115
 
@@ -134,11 +121,11 @@ PLATFORMS
134
121
  DEPENDENCIES
135
122
  activerecord-ejection_seat!
136
123
  debug
124
+ minitest
137
125
  rake
138
- rspec
139
126
  rubocop
127
+ rubocop-minitest
140
128
  rubocop-rake
141
- rubocop-rspec
142
129
  rubocop-sorbet
143
130
  ruby-lsp
144
131
  sorbet
@@ -149,4 +136,4 @@ DEPENDENCIES
149
136
  tapioca
150
137
 
151
138
  BUNDLED WITH
152
- 2.3.26
139
+ 2.4.4
data/README.md CHANGED
@@ -20,13 +20,21 @@ If bundler is not being used to manage dependencies, install the gem by executin
20
20
 
21
21
  ## Usage
22
22
 
23
- Let's say you have an `ActiveRecord` model for a `User` with a string `name` column and an integer `age` column. Another way to express this would be a `User` type that is a simple, typed struct.
23
+ Let's say you have an `ActiveRecord` model for a `User` with a string `name` column, an integer `age` column and a `role` enum column. Another way to express this would be a `User` type that is a simple, typed struct.
24
24
 
25
25
  ```ruby
26
26
  module Types
27
+ class UserRoles < T::Enum
28
+ enums do
29
+ Admin = new("admin")
30
+ Member = new("member")
31
+ end
32
+ end
33
+
27
34
  class User < T::Struct
28
35
  const :name, String
29
36
  const :age, Integer
37
+ const :role, UserRoles
30
38
  end
31
39
  end
32
40
  ```
@@ -35,6 +43,8 @@ In our model, we can specify an ejection to this type.
35
43
 
36
44
  ```ruby
37
45
  class User
46
+ enum :role, { admin: "admin", member: "member" }
47
+
38
48
  ejects_to Types::User
39
49
  end
40
50
  ```
@@ -42,17 +52,17 @@ end
42
52
  Now, we have two new methods available on `User`. First, we can eject from a `User` instance to a `Types::User`.
43
53
 
44
54
  ```ruby
45
- User.new(name: "Max", age: 28).eject
46
- # => Types::User(name: "Max", age: 28)
47
- User.new(name: "Max", age: 28).to_struct # alias
55
+ User.new(name: "Max", age: 28, role: "admin").eject
56
+ # => Types::User(name: "Max", age: 28, role: Types::UserRoles::Admin)
57
+ User.new(name: "Max", age: 28, role: "admin").to_struct # alias
48
58
  ```
49
59
 
50
60
  Second, we can buckle into the `User` model with a `Types::User`.
51
61
 
52
62
  ```ruby
53
- user_struct = Types::User.new(name: "Max", age: 28)
63
+ user_struct = Types::User.new(name: "Max", age: 28, role: Types::UserRoles::Admin)
54
64
  User.buckle(user_struct)
55
- # => User(name: "Max", age: 28)
65
+ # => User(name: "Max", age: 28, role: "admin")
56
66
  User.from_struct(user_struct) # alias
57
67
  ```
58
68
 
data/Rakefile CHANGED
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
4
+ require "minitest/test_task"
5
5
 
6
- RSpec::Core::RakeTask.new(:spec)
6
+ Minitest::TestTask.create do |t|
7
+ t.test_globs = ["test/**/*_test.rb"]
8
+ end
7
9
 
8
10
  require "rubocop/rake_task"
9
11
 
10
- RuboCop::RakeTask.new do |t|
11
- t.options = ["-A"]
12
- end
12
+ RuboCop::RakeTask.new
13
13
 
14
14
  desc "Run tapioca compilers"
15
15
  task :tapioca do
@@ -21,4 +21,4 @@ task :sorbet do
21
21
  sh "bundle exec srb tc"
22
22
  end
23
23
 
24
- task default: %i[rubocop sorbet spec]
24
+ task default: %i[rubocop:autocorrect_all sorbet test]
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/activerecord-ejection_seat/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "activerecord-ejection_seat"
7
+ spec.version = ActiveRecord::EjectionSeat::VERSION
8
+ spec.authors = ["Max VelDink"]
9
+ spec.email = ["max@audaciousaardvark.com"]
10
+
11
+ spec.summary = "Eject from an ActiveRecord model to a Sorbet T::Struct, or buckle back in."
12
+ spec.description = "When working with ActiveRecord models, sometimes you want to eject to a simpler, safer object. \
13
+ Enter Sorbet's T::Struct. This gem makes it much easier to target a \
14
+ Sorbet T::Struct and eject from an ActiveRecord model into the struct. \
15
+ It also allows you to buckle in from a simple struct to a new ActiveRecord model instance."
16
+ spec.homepage = "https://github.com/AudaciousAardvark/activerecord-ejection_seat"
17
+ spec.license = "MIT"
18
+ spec.required_ruby_version = ">= 3.0.0"
19
+
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+ spec.metadata["rubygems_mfa_required"] = "true"
22
+
23
+ spec.metadata["homepage_uri"] = spec.homepage
24
+ spec.metadata["source_code_uri"] = spec.homepage
25
+ spec.metadata["changelog_uri"] = "https://github.com/AudaciousAardvark/activerecord-ejection_seat/blob/main/CHANGELOG.md"
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
+ spec.files = Dir.chdir(__dir__) do
30
+ `git ls-files -z`.split("\x0").reject do |f|
31
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
32
+ end
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_runtime_dependency "activerecord", ">= 6.0"
39
+ spec.add_runtime_dependency "sorbet-runtime"
40
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # Creates initialization payload for targeted ActiveRecord model
5
+ class AttributesBuilder
6
+ extend T::Sig
7
+
8
+ sig { params(struct: T::Struct, target_model: T.class_of(ActiveRecord::Base)).void }
9
+ def initialize(struct:, target_model:)
10
+ @struct = struct
11
+ @target_model = target_model
12
+ end
13
+
14
+ sig { returns(T::Hash[String, T.untyped]) }
15
+ def build
16
+ attrs = struct.serialize
17
+ props = attrs.keys & target_model.column_names.map { |name| name.delete_suffix("_id") }
18
+
19
+ attrs.slice(*props).to_h { |key, value| build_attribute(name: key, value: value) }
20
+ end
21
+
22
+ private
23
+
24
+ sig { params(name: String, value: T.untyped).returns(T::Array[T.untyped]) }
25
+ def build_attribute(name:, value:)
26
+ if value.is_a?(Hash)
27
+ association_class = target_model.reflect_on_all_associations.find do |association|
28
+ association.name.to_s == name
29
+ end&.klass
30
+
31
+ value = association_class&.new(value)
32
+ end
33
+
34
+ [name, value]
35
+ end
36
+
37
+ sig { returns(T::Struct) }
38
+ attr_reader :struct
39
+
40
+ sig { returns(T.class_of(ActiveRecord::Base)) }
41
+ attr_reader :target_model
42
+ end
@@ -1,8 +1,6 @@
1
1
  # typed: false
2
2
  # frozen_string_literal: true
3
3
 
4
- require "active_record"
5
-
6
4
  module ActiveRecord
7
5
  module EjectionSeat
8
6
  # Defines `#eject` `.buckle` methods for going between ActiveRecord models and Sorbet T::Structs.
@@ -16,10 +14,12 @@ module ActiveRecord
16
14
 
17
15
  def define_eject_method(klass)
18
16
  define_method(:eject) do
19
- attrs = attributes.deep_symbolize_keys
20
- attribute_props = klass.props.keys & attrs.keys
21
-
22
- klass.new(PropsBuilder.new(attrs.slice(*attribute_props), klass.props).build)
17
+ klass.new(
18
+ PropsBuilder.new(
19
+ model: self,
20
+ target_struct: klass
21
+ ).build
22
+ )
23
23
  end
24
24
 
25
25
  alias_method :to_struct, :eject
@@ -29,10 +29,7 @@ module ActiveRecord
29
29
  define_singleton_method(:buckle) do |struct|
30
30
  raise ArgumentError if struct.class != klass
31
31
 
32
- attrs = struct.serialize
33
- props = attrs.keys & column_names
34
-
35
- new(attrs.slice(*props))
32
+ new(AttributesBuilder.new(struct: struct, target_model: self).build)
36
33
  end
37
34
 
38
35
  singleton_class.send :alias_method, :from_struct, :buckle
@@ -1,44 +1,56 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
-
6
4
  # Creates initialization payload for targeted T::Struct
7
5
  class PropsBuilder
8
6
  extend T::Sig
9
7
 
10
- Attributes = T.type_alias { T::Hash[Symbol, T.untyped] }
11
-
12
8
  sig do
13
9
  params(
14
- attributes_from_model: Attributes,
15
- target_props: Attributes
10
+ model: ActiveRecord::Base,
11
+ target_struct: T.class_of(T::Struct)
16
12
  ).void
17
13
  end
18
- def initialize(attributes_from_model, target_props)
19
- @attributes_from_model = attributes_from_model
20
- @target_props = target_props
14
+ def initialize(model:, target_struct:)
15
+ @model = model
16
+ @target_struct = target_struct
21
17
  end
22
18
 
23
- sig { returns(Attributes) }
19
+ sig { returns(T::Hash[Symbol, T.untyped]) }
24
20
  def build
25
- built_props = {}
26
-
27
- attributes_from_model.each do |k, v|
28
- prop_type = target_props.dig(k, :type)
21
+ target_struct.props.keys.each_with_object({}) do |prop_name, returned_props|
22
+ attribute = model.respond_to?(prop_name) ? build_attribute(prop_name) : nil
29
23
 
30
- built_props[k] = if prop_type < T::Enum
31
- prop_type.deserialize(v)
32
- else
33
- v
34
- end
24
+ returned_props[prop_name] = attribute
35
25
  end
36
-
37
- built_props
38
26
  end
39
27
 
40
28
  private
41
29
 
42
- sig { returns(Attributes) }
43
- attr_reader :attributes_from_model, :target_props
30
+ sig { params(prop_name: Symbol).returns(T.untyped) }
31
+ def build_attribute(prop_name)
32
+ attribute = model.send(prop_name)
33
+ prop_type = target_struct.props.dig(prop_name, :type)
34
+
35
+ cast_attribute(attribute, prop_type)
36
+ end
37
+
38
+ sig { params(attribute: T.untyped, prop_type: Class).returns(T.untyped) }
39
+ def cast_attribute(attribute, prop_type)
40
+ if prop_type < T::Enum
41
+ prop_type.deserialize(attribute)
42
+ elsif prop_type < T::Struct
43
+ prop_type.new(PropsBuilder.new(model: attribute, target_struct: prop_type).build)
44
+ else
45
+ attribute
46
+ end
47
+ rescue KeyError
48
+ nil
49
+ end
50
+
51
+ sig { returns(ActiveRecord::Base) }
52
+ attr_reader :model
53
+
54
+ sig { returns(T.class_of(T::Struct)) }
55
+ attr_reader :target_struct
44
56
  end
@@ -1,8 +1,8 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ActiveRecord
5
5
  module EjectionSeat
6
- VERSION = "0.2.0"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
@@ -1,12 +1,15 @@
1
1
  # rubocop:disable Naming/FileName
2
2
  # rubocop:enable Naming/FileName
3
- # typed: false
3
+ # typed: strict
4
4
  # frozen_string_literal: true
5
5
 
6
+ require "active_record"
7
+ require "sorbet-runtime"
8
+ require "active_support/lazy_load_hooks"
9
+
6
10
  require_relative "activerecord-ejection_seat/version"
7
11
  require_relative "activerecord-ejection_seat/props_builder"
8
-
9
- require "active_support/lazy_load_hooks"
12
+ require_relative "activerecord-ejection_seat/attributes_builder"
10
13
 
11
14
  ActiveSupport.on_load(:active_record) do
12
15
  require "activerecord-ejection_seat/ejectable"