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: 76dd7495e0e2e855e708737a875ff879a8ec840d68f5c80ad4733ece3b72dda6
4
- data.tar.gz: '09b38711104836c6c222a1663ddf4e7248ae784b727b597ed61f6436ab2bf17b'
3
+ metadata.gz: 4212b6fcecceb21ae133688c2a3332d29b8c0f5760f5a22a8ba3450037a95b20
4
+ data.tar.gz: 4b96fde3db82d00a3c94153cb9b212f1091bd0040d7d9771d300f2a0a0147d6f
5
5
  SHA512:
6
- metadata.gz: 06fc0e959dedecab41a3c0d48d846eda454bd69c48fe8042cad1378c408fd919f688c2e07d5a26d675316556210ae25796585bc56438c8c66e3092c52fe3b1f6
7
- data.tar.gz: bd35d99fc113f26d2b49e1fde335db7c28af90e7985ea0d2c60dc9dd8188e05cc2c4102bbe4edf5c45998d2b5cc96cd8f00bcd8974acf858450db479432c84f8
6
+ metadata.gz: 262b75fdf718925e7772b7c1dd4e49ab6b73f6706be97081433712a4f3efef524b1677194ff5376415fe32e770bfa080afd5b347c2ccf78786517dfe5a21a9fb
7
+ data.tar.gz: 47c40f2a351cd49fe9bc5e0488d37ce79bcb3d30332b3aabbaff681ef7ac7f937d81d01c60d6b7f8393d3fc28f7fa92fb9b4d5eb99ae607d95d0389deea12764
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_record-associated_object (0.8.1)
4
+ active_record-associated_object (0.8.3)
5
5
  activerecord (>= 6.1)
6
6
 
7
7
  GEM
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 "object_association.setup" do
10
- ActiveSupport.on_load :active_job do
11
- require "active_job/performs"
12
- ActiveRecord::AssociatedObject.extend ActiveJob::Performs
13
- rescue LoadError
14
- # We haven't bundled active_job-performs, so we're continuing without it.
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class AssociatedObject
5
- VERSION = "0.8.1"
5
+ VERSION = "0.8.3"
6
6
  end
7
7
  end
@@ -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.downcase.underscore
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.1
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-05-19 00:00:00.000000000 Z
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.6
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