active_record-associated_object 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4212b6fcecceb21ae133688c2a3332d29b8c0f5760f5a22a8ba3450037a95b20
|
4
|
+
data.tar.gz: 4b96fde3db82d00a3c94153cb9b212f1091bd0040d7d9771d300f2a0a0147d6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 262b75fdf718925e7772b7c1dd4e49ab6b73f6706be97081433712a4f3efef524b1677194ff5376415fe32e770bfa080afd5b347c2ccf78786517dfe5a21a9fb
|
7
|
+
data.tar.gz: 47c40f2a351cd49fe9bc5e0488d37ce79bcb3d30332b3aabbaff681ef7ac7f937d81d01c60d6b7f8393d3fc28f7fa92fb9b4d5eb99ae607d95d0389deea12764
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -60,7 +60,7 @@ We've fixed this so you don't need to care, but this is what's happening.
|
|
60
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`.
|
61
61
|
|
62
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.
|
63
|
+
> You can pass multiple names too: `has_object :seats, :entitlements, :publisher, :classified, :fortification`. I recommend `-s`, `-[i]er`, `-[i]ed` and `-ion` as the general naming conventions for your Associated Objects.
|
64
64
|
|
65
65
|
> [!TIP]
|
66
66
|
> Plural Associated Object names are also supported: `Account.has_object :seats` will look up `Account::Seats`.
|
@@ -85,6 +85,28 @@ class Post::Publisher < ActiveRecord::AssociatedObject
|
|
85
85
|
end
|
86
86
|
```
|
87
87
|
|
88
|
+
### See Associated Objects in action
|
89
|
+
|
90
|
+
#### RubyVideo.dev
|
91
|
+
|
92
|
+
The team at https://www.rubyvideo.dev has been using Associated Objects to clarify the boundaries of their Active Records and collaborator Associated Objects.
|
93
|
+
|
94
|
+
See the usage in the source here:
|
95
|
+
|
96
|
+
- [`ActiveRecord::AssociatedObject` instances](https://github.com/search?q=repo%3Aadrienpoly%2Frubyvideo%20ActiveRecord%3A%3AAssociatedObject&type=code)
|
97
|
+
- [`has_object` calls](https://github.com/search?q=repo%3Aadrienpoly%2Frubyvideo+has_object&type=code)
|
98
|
+
|
99
|
+
#### Flipper
|
100
|
+
|
101
|
+
The team at [Flipper](https://www.flippercloud.io) used Associated Objects to help keep their new billing structure clean.
|
102
|
+
|
103
|
+
You can see real life examples in these blog posts:
|
104
|
+
|
105
|
+
- [Organizing Rails Code with ActiveRecord Associated Objects](https://garrettdimon.com/journal/posts/organizing-rails-code-with-activerecord-associated-objects)
|
106
|
+
- [Data Modeling Entitlements and Pricing for SaaS Applications](https://garrettdimon.com/journal/posts/data-modeling-saas-entitlements-and-pricing)
|
107
|
+
|
108
|
+
If your team is using Associated Objects, we're more than happy to feature any write ups here.
|
109
|
+
|
88
110
|
### Use the generator to help write Associated Objects
|
89
111
|
|
90
112
|
To set up the `Post::Publisher` from above, you can call `bin/rails generate associated Post::Publisher`.
|
@@ -229,6 +251,68 @@ class Post::PublisherTest < ActiveSupport::TestCase
|
|
229
251
|
end
|
230
252
|
```
|
231
253
|
|
254
|
+
### Polymorphic Associated Objects
|
255
|
+
|
256
|
+
If you want to share logic between associated objects, you can do so via standard Ruby modules:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
# app/models/pricing.rb
|
260
|
+
module Pricing
|
261
|
+
# If you need to share an `extension` across associated objects you can override `Module::included` like this:
|
262
|
+
def self.included(object) = object.extension do
|
263
|
+
# Add common integration methods onto `Account`/`User` when the module is included.
|
264
|
+
# See the `extension` block in the `Extending` section above for an example.
|
265
|
+
end
|
266
|
+
|
267
|
+
def price_set?
|
268
|
+
# Instead of referring to `account` or `user`, use the `record` method to target either.
|
269
|
+
record.price_cents.positive?
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# app/models/account/pricing.rb
|
274
|
+
class Account::Pricing < ActiveRecord::AssociatedObject
|
275
|
+
include ::Pricing
|
276
|
+
end
|
277
|
+
|
278
|
+
# app/models/user/pricing.rb
|
279
|
+
class User::Pricing < ActiveRecord::AssociatedObject
|
280
|
+
include ::Pricing
|
281
|
+
end
|
282
|
+
```
|
283
|
+
|
284
|
+
Now we can call `account.pricing.price_set?` & `user.pricing.price_set?`.
|
285
|
+
|
286
|
+
> [!NOTE]
|
287
|
+
> Polymorphic Associated Objects are definitely a more advanced topic,
|
288
|
+
> so you need to know your Ruby module hierarchy and how to track what `self` changes to fairly well.
|
289
|
+
|
290
|
+
#### Using `ActiveSupport::Concern` as an alternative
|
291
|
+
|
292
|
+
If you prefer the look of Active Support concerns, here's the equivalent to the above Ruby module:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
# app/models/pricing.rb
|
296
|
+
module Pricing
|
297
|
+
extend ActiveSupport::Concern
|
298
|
+
|
299
|
+
included do
|
300
|
+
extension do
|
301
|
+
# Add common integration methods onto `Account`/`User` when the concern is included.
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def price_set?
|
306
|
+
# Instead of referring to `account` or `user`, use the `record` method to target either.
|
307
|
+
record.price_cents.positive?
|
308
|
+
end
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
Active Support concerns have some extra features that standard Ruby modules don't, like support for deeply-nested concerns and `class_methods do`.
|
313
|
+
|
314
|
+
In this case, if you're reaching for those, you're probably building something too intricate and potentially brittle.
|
315
|
+
|
232
316
|
### Active Job integration via GlobalID
|
233
317
|
|
234
318
|
Associated Objects include `GlobalID::Identification` and have automatic Active Job serialization support that looks like this:
|
@@ -6,14 +6,14 @@ class ActiveRecord::AssociatedObject::Railtie < Rails::Railtie
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
initializer "
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
9
|
+
initializer "active_job.performs" do
|
10
|
+
require "active_job/performs"
|
11
|
+
ActiveRecord::AssociatedObject.extend ActiveJob::Performs if defined?(ActiveJob::Performs)
|
12
|
+
rescue LoadError
|
13
|
+
# We haven't bundled active_job-performs, so we're continuing without it.
|
14
|
+
end
|
16
15
|
|
16
|
+
initializer "object_association.setup" do
|
17
17
|
ActiveSupport.on_load :active_record do
|
18
18
|
require "active_record/associated_object/object_association"
|
19
19
|
include ActiveRecord::AssociatedObject::ObjectAssociation
|
@@ -8,6 +8,7 @@ class AssociatedGenerator < Rails::Generators::NamedBase
|
|
8
8
|
|
9
9
|
def connect_associated_object
|
10
10
|
record_file = "#{destination_root}/app/models/#{record_path}.rb"
|
11
|
+
|
11
12
|
raise "Record class '#{record_klass}' does not exist" unless File.exist?(record_file)
|
12
13
|
|
13
14
|
inject_into_class record_file, record_klass do
|
@@ -20,8 +21,8 @@ class AssociatedGenerator < Rails::Generators::NamedBase
|
|
20
21
|
# The `:name` argument can handle model names, but associated object class names aren't singularized.
|
21
22
|
# So these record and associated_object methods prevent that.
|
22
23
|
def record_path = record_klass.downcase.underscore
|
23
|
-
def record_klass = name.deconstantize
|
24
|
+
def record_klass = name.camelize.deconstantize
|
24
25
|
|
25
|
-
def associated_object_path = associated_object_class.
|
26
|
-
def associated_object_class = name.demodulize
|
26
|
+
def associated_object_path = associated_object_class.underscore
|
27
|
+
def associated_object_class = name.camelize.demodulize
|
27
28
|
end
|
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.8.
|
4
|
+
version: 0.8.3
|
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: 2024-
|
11
|
+
date: 2024-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
|
-
rubygems_version: 3.5.
|
72
|
+
rubygems_version: 3.5.18
|
73
73
|
signing_key:
|
74
74
|
specification_version: 4
|
75
75
|
summary: Associate a Ruby PORO with an Active Record class and have it quack like
|