active_record-associated_object 0.6.0 → 0.7.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: ca7c634684913b4f2f55b96e54036df0590fe963e06beb15bf98a641a450c5e1
4
- data.tar.gz: 279780001730e8d339d15dfa3f6659be157169d74a1e4949132c5d7a2b8e596a
3
+ metadata.gz: d24bf37946a6b4e5ddcb1626e90eb91e27932ae8d9f8f29c10c095ce3c53897c
4
+ data.tar.gz: 7f7d23788109d020fb1b010e8e6f3956880dfeb012c517dfc43e1f5c7862fd6c
5
5
  SHA512:
6
- metadata.gz: e4b86186ef2aa86f31535313a22adf2835694829ba3a53c3d6b5847429ab5a002c3e09f36e3718f756370ca12f3d335b7851b42a8939d234f8de00d71970e255
7
- data.tar.gz: ff2ac40c52831018085167aa42f9212b4cecdd7cb5dac199d9f258278553752b7cf3b40df2295d240df65bd6cc685981dd82d710b947d32b8192b5b8d39ee3eb
6
+ metadata.gz: 72675998afa72ea4117fd3601e9df6c9dbd2f62ae1541e1b6677903dd5c32109bd06c281e9be98d7742b92dcf6e48f2792764a5c661729ef2df95d6ce54cd2c4
7
+ data.tar.gz: 3d4244067099adec3ac04a185019bd99aba3078578ede15315893eb6c8f6b9a98cf8cd6356e74e37a395f39ec20013e83529ab6f9766dd014f154d3d03591718
data/Gemfile.lock CHANGED
@@ -1,39 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_record-associated_object (0.6.0)
4
+ active_record-associated_object (0.7.0)
5
5
  activerecord (>= 6.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actionpack (7.1.1)
11
- actionview (= 7.1.1)
12
- activesupport (= 7.1.1)
10
+ actionpack (7.1.2)
11
+ actionview (= 7.1.2)
12
+ activesupport (= 7.1.2)
13
13
  nokogiri (>= 1.8.5)
14
+ racc
14
15
  rack (>= 2.2.4)
15
16
  rack-session (>= 1.0.1)
16
17
  rack-test (>= 0.6.3)
17
18
  rails-dom-testing (~> 2.2)
18
19
  rails-html-sanitizer (~> 1.6)
19
- actionview (7.1.1)
20
- activesupport (= 7.1.1)
20
+ actionview (7.1.2)
21
+ activesupport (= 7.1.2)
21
22
  builder (~> 3.1)
22
23
  erubi (~> 1.11)
23
24
  rails-dom-testing (~> 2.2)
24
25
  rails-html-sanitizer (~> 1.6)
25
- active_job-performs (0.2.0)
26
+ active_job-performs (0.3.0)
26
27
  activejob (>= 6.1)
27
- activejob (7.1.1)
28
- activesupport (= 7.1.1)
28
+ activejob (7.1.2)
29
+ activesupport (= 7.1.2)
29
30
  globalid (>= 0.3.6)
30
- activemodel (7.1.1)
31
- activesupport (= 7.1.1)
32
- activerecord (7.1.1)
33
- activemodel (= 7.1.1)
34
- activesupport (= 7.1.1)
31
+ activemodel (7.1.2)
32
+ activesupport (= 7.1.2)
33
+ activerecord (7.1.2)
34
+ activemodel (= 7.1.2)
35
+ activesupport (= 7.1.2)
35
36
  timeout (>= 0.4.0)
36
- activesupport (7.1.1)
37
+ activesupport (7.1.2)
37
38
  base64
38
39
  bigdecimal
39
40
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -43,45 +44,45 @@ GEM
43
44
  minitest (>= 5.1)
44
45
  mutex_m
45
46
  tzinfo (~> 2.0)
46
- base64 (0.1.1)
47
- bigdecimal (3.1.4)
47
+ base64 (0.2.0)
48
+ bigdecimal (3.1.5)
48
49
  builder (3.2.4)
49
50
  concurrent-ruby (1.2.2)
50
51
  connection_pool (2.4.1)
51
52
  crass (1.0.6)
52
- debug (1.8.0)
53
- irb (>= 1.5.0)
54
- reline (>= 0.3.1)
55
- drb (2.1.1)
53
+ debug (1.9.1)
54
+ irb (~> 1.10)
55
+ reline (>= 0.3.8)
56
+ drb (2.2.0)
56
57
  ruby2_keywords
57
58
  erubi (1.12.0)
58
59
  globalid (1.2.1)
59
60
  activesupport (>= 6.1)
60
61
  i18n (1.14.1)
61
62
  concurrent-ruby (~> 1.0)
62
- io-console (0.6.0)
63
- irb (1.8.3)
63
+ io-console (0.7.1)
64
+ irb (1.11.0)
64
65
  rdoc
65
66
  reline (>= 0.3.8)
66
- kredis (1.6.0)
67
+ kredis (1.7.0)
67
68
  activemodel (>= 6.0.0)
68
69
  activesupport (>= 6.0.0)
69
70
  redis (>= 4.2, < 6)
70
- loofah (2.21.4)
71
+ loofah (2.22.0)
71
72
  crass (~> 1.0.2)
72
73
  nokogiri (>= 1.12.0)
73
74
  minitest (5.20.0)
74
75
  minitest-sprint (1.2.2)
75
76
  path_expander (~> 1.1)
76
- mutex_m (0.1.2)
77
- nokogiri (1.15.4-arm64-darwin)
77
+ mutex_m (0.2.0)
78
+ nokogiri (1.16.0-arm64-darwin)
78
79
  racc (~> 1.4)
79
- nokogiri (1.15.4-x86_64-linux)
80
+ nokogiri (1.16.0-x86_64-linux)
80
81
  racc (~> 1.4)
81
82
  path_expander (1.1.1)
82
- psych (5.1.1.1)
83
+ psych (5.1.2)
83
84
  stringio
84
- racc (1.7.1)
85
+ racc (1.7.3)
85
86
  rack (3.0.8)
86
87
  rack-session (2.0.0)
87
88
  rack (>= 3.0.0)
@@ -97,29 +98,29 @@ GEM
97
98
  rails-html-sanitizer (1.6.0)
98
99
  loofah (~> 2.21)
99
100
  nokogiri (~> 1.14)
100
- railties (7.1.1)
101
- actionpack (= 7.1.1)
102
- activesupport (= 7.1.1)
101
+ railties (7.1.2)
102
+ actionpack (= 7.1.2)
103
+ activesupport (= 7.1.2)
103
104
  irb
104
105
  rackup (>= 1.0.0)
105
106
  rake (>= 12.2)
106
107
  thor (~> 1.0, >= 1.2.2)
107
108
  zeitwerk (~> 2.6)
108
109
  rake (13.1.0)
109
- rdoc (6.5.0)
110
+ rdoc (6.6.2)
110
111
  psych (>= 4.0.0)
111
112
  redis (5.0.8)
112
113
  redis-client (>= 0.17.0)
113
- redis-client (0.18.0)
114
+ redis-client (0.19.1)
114
115
  connection_pool
115
- reline (0.3.9)
116
+ reline (0.4.2)
116
117
  io-console (~> 0.5)
117
118
  ruby2_keywords (0.0.5)
118
- sqlite3 (1.6.7-arm64-darwin)
119
- sqlite3 (1.6.7-x86_64-linux)
120
- stringio (3.0.8)
119
+ sqlite3 (1.7.0-arm64-darwin)
120
+ sqlite3 (1.7.0-x86_64-linux)
121
+ stringio (3.1.0)
121
122
  thor (1.3.0)
122
- timeout (0.4.0)
123
+ timeout (0.4.1)
123
124
  tzinfo (2.0.6)
124
125
  concurrent-ruby (~> 1.0)
125
126
  webrick (1.8.1)
@@ -129,6 +130,7 @@ PLATFORMS
129
130
  arm64-darwin-20
130
131
  arm64-darwin-21
131
132
  arm64-darwin-22
133
+ arm64-darwin-23
132
134
  x86_64-linux
133
135
 
134
136
  DEPENDENCIES
@@ -144,4 +146,4 @@ DEPENDENCIES
144
146
  sqlite3
145
147
 
146
148
  BUNDLED WITH
147
- 2.4.21
149
+ 2.5.4
data/README.md CHANGED
@@ -49,13 +49,19 @@ class Post::Publisher
49
49
  end
50
50
 
51
51
  class Post < ApplicationRecord
52
- def publisher = @publisher ||= Post::Publisher.new(self)
52
+ def publisher = (@associated_objects ||= {})[:publisher] ||= Post::Publisher.new(self)
53
53
  end
54
54
  ```
55
55
 
56
+ Note: due to Ruby's Object Shapes, we use a single `@associated_objects` instance variable that's assigned to `nil` on `Post.new`. This prevents Active Record's from ballooning into many different shapes in Ruby's internals.
57
+ We've fixed this so you don't need to care, but this is what's happening.
58
+
56
59
  > [!TIP]
57
60
  > `has_object` only requires a namespace and an initializer that takes a single argument. The above `Post::Publisher` is perfectly valid as an Associated Object — same goes for `class Post::Publisher < Data.define(:post); end`.
58
61
 
62
+ > [!TIP]
63
+ > You can pass multiple names too: `has_object :publisher, :classified, :fortification`. I recommend `-[i]er`, `-[i]ed` and `-ion` as the general naming conventions for your Associated Objects.
64
+
59
65
  See how we're always expecting a link to the model, here `post`?
60
66
 
61
67
  Because of that, you can rely on `post` from the associated object:
@@ -86,9 +92,9 @@ So `has_object` can state this and forward those callbacks onto the Associated O
86
92
 
87
93
  ```ruby
88
94
  class Post < ActiveRecord::Base
89
- # Passing `true`
90
- has_object :publisher, after_create_commit: :publish,
91
- after_touch: true, before_destroy: :prevent_errant_post_destroy
95
+ # Passing `true` forwards the same name, e.g. `after_touch`.
96
+ has_object :publisher, after_touch: true, after_create_commit: :publish,
97
+ before_destroy: :prevent_errant_post_destroy
92
98
 
93
99
  # The above is the same as writing:
94
100
  after_create_commit { publisher.publish }
@@ -111,6 +117,69 @@ class Post::Publisher < ActiveRecord::AssociatedObject
111
117
  end
112
118
  ```
113
119
 
120
+ ### Extending the Active Record from within the Associated Object
121
+
122
+ Since `has_object` eager-loads the Associated Object class, you can also move
123
+ any integrating code into a provided `extension` block:
124
+
125
+ > [!NOTE]
126
+ > Technically, `extension` is just `Post.class_eval` but with syntactic sugar.
127
+
128
+ ```ruby
129
+ class Post::Publisher < ActiveRecord::AssociatedObject
130
+ extension do
131
+ # Here we're within Post and can extend it:
132
+ has_many :contracts, dependent: :destroy do
133
+ def signed? = all?(&:signed?)
134
+ end
135
+
136
+ def self.with_contracts = includes(:contracts)
137
+
138
+ after_create_commit :publish_later, if: -> { contracts.signed? }
139
+
140
+ # An integrating method that operates on `publisher`.
141
+ private def publish_later = publisher.publish_later
142
+ end
143
+ end
144
+ ```
145
+
146
+ This is meant as an alternative to having a wrapping `ActiveSupport::Concern` in yet-another file like this:
147
+
148
+ ```ruby
149
+ class Post < ApplicationRecord
150
+ include Published
151
+ end
152
+
153
+ # app/models/post/published.rb
154
+ module Post::Published
155
+ extend ActiveSupport::Concern
156
+
157
+ included do
158
+ has_many :contracts, dependent: :destroy do
159
+ def signed? = all?(&:signed?)
160
+ end
161
+
162
+ has_object :publisher
163
+ after_create_commit :publish_later, if: -> { contracts.signed? }
164
+ end
165
+
166
+ class_methods do
167
+ def with_contracts = includes(:contracts)
168
+ end
169
+
170
+ # An integrating method that operates on `publisher`.
171
+ private def publish_later = publisher.publish_later
172
+ end
173
+ ```
174
+
175
+ > [!NOTE]
176
+ > Notice how in the `extension` version you don't need to:
177
+ >
178
+ > - have a naming convention for Concerns and where to place them.
179
+ > - look up two files to read the feature (the concern and the associated object).
180
+ > - wrap integrating code in an `included` block.
181
+ > - wrap class methods in a `class_methods` block.
182
+
114
183
  ### Primary Benefit: Organization through Convention
115
184
 
116
185
  The primary benefit for right now is that by focusing the concept of namespaced Collaborator Objects through Associated Objects, you will start seeing them when you're modelling new features and it'll change how you structure and write your apps.
@@ -231,7 +300,20 @@ performs :publish, queue_as: :important, discard_on: SomeError do
231
300
  end
232
301
  ```
233
302
 
234
- See the `ActiveJob::Performs` README for more details.
303
+ See [the `ActiveJob::Performs` README](https://github.com/kaspth/active_job-performs) for more details.
304
+
305
+ ### Automatic Kredis integration
306
+
307
+ We've got automatic Kredis integration for Associated Objects, so you can use any `kredis_*` type just like in Active Record classes:
308
+
309
+ ```ruby
310
+ class Post::Publisher < ActiveRecord::AssociatedObject
311
+ kredis_datetime :publish_at # Uses a namespaced "post:publishers:<post_id>:publish_at" key.
312
+ end
313
+ ```
314
+
315
+ > [!NOTE]
316
+ > Under the hood, this reuses the same info we needed for automatic Active Job support. Namely, the Active Record class, here `Post`, and its `id`.
235
317
 
236
318
  ### Namespaced models
237
319
 
@@ -1,4 +1,6 @@
1
1
  module ActiveRecord::AssociatedObject::ObjectAssociation
2
+ def self.included(klass) = klass.extend(ClassMethods)
3
+
2
4
  using Module.new {
3
5
  refine Module do
4
6
  def extend_source_from(chunks, &block)
@@ -9,15 +11,22 @@ module ActiveRecord::AssociatedObject::ObjectAssociation
9
11
  end
10
12
  }
11
13
 
12
- def has_object(*names, **callbacks)
13
- extend_source_from(names) do |name|
14
- "def #{name}; @#{name} ||= #{self.name}::#{name.to_s.classify}.new(self); end"
15
- end
14
+ module ClassMethods
15
+ def has_object(*names, **callbacks)
16
+ extend_source_from(names) do |name|
17
+ "def #{name}; (@associated_objects ||= {})[:#{name}] ||= #{name.to_s.classify}.new(self); end"
18
+ end
16
19
 
17
- extend_source_from(names) do |name|
18
- callbacks.map do |callback, method|
19
- "#{callback} { #{name}.#{method == true ? callback : method} }"
20
+ extend_source_from(names) do |name|
21
+ callbacks.map do |callback, method|
22
+ "#{callback} { #{name}.#{method == true ? callback : method} }"
23
+ end
20
24
  end
21
25
  end
22
26
  end
27
+
28
+ def init_internals
29
+ @associated_objects = nil
30
+ super
31
+ end
23
32
  end
@@ -16,7 +16,7 @@ class ActiveRecord::AssociatedObject::Railtie < Rails::Railtie
16
16
 
17
17
  ActiveSupport.on_load :active_record do
18
18
  require "active_record/associated_object/object_association"
19
- extend ActiveRecord::AssociatedObject::ObjectAssociation
19
+ include ActiveRecord::AssociatedObject::ObjectAssociation
20
20
  end
21
21
  end
22
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class AssociatedObject
5
- VERSION = "0.6.0"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
@@ -15,6 +15,10 @@ class ActiveRecord::AssociatedObject
15
15
  klass.delegate :record_klass, :attribute_name, to: :class
16
16
  end
17
17
 
18
+ def extension(&block)
19
+ record_klass.class_eval(&block)
20
+ end
21
+
18
22
  def respond_to_missing?(...) = record_klass.respond_to?(...) || super
19
23
  delegate :unscoped, :transaction, :primary_key, to: :record_klass
20
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record-associated_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-18 00:00:00.000000000 Z
11
+ date: 2024-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -65,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
65
  - !ruby/object:Gem::Version
66
66
  version: '0'
67
67
  requirements: []
68
- rubygems_version: 3.4.22
68
+ rubygems_version: 3.5.3
69
69
  signing_key:
70
70
  specification_version: 4
71
71
  summary: Associate a Ruby PORO with an Active Record class and have it quack like