active_record-associated_object 0.2.0 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +43 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +8 -1
- data/README.md +79 -13
- data/Rakefile +0 -9
- data/active_record-associated_object.gemspec +1 -2
- data/lib/active_record/associated_object/object_association.rb +12 -6
- data/lib/active_record/associated_object/railtie.rb +6 -1
- data/lib/active_record/associated_object/version.rb +1 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20d91687ec83cbc70efb318377c15ce1e2e79b28d350322b0d91ad6618073cc2
|
4
|
+
data.tar.gz: 59f19eea143da23bd980f0d6bca5a96670e13c7859d0db860741bf314d949822
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a41fc155e625c0d04d01f39a55a6c80155b8e275abb1c9be9ef5fac6c927cd6c36b83a13a9f621c73c344b333b246bd86e76bb49773579b2605fb1d2ede636b3
|
7
|
+
data.tar.gz: 3de159d609ac4ab8eedbe03d681cd6d8295ca4831c7eb4430ca760f86dfa243906bd9c7d62cc2cd60cf44ba0aa271116114703bd59a784780260d4b961f3e725
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,46 @@
|
|
1
|
+
## [0.4.0] - 2022-09-25
|
2
|
+
|
3
|
+
- Extract `performs` into the `active_job-performs` gem with some fixes and extra features, but include it as a dependency.
|
4
|
+
|
5
|
+
## [0.3.0] - 2022-09-25
|
6
|
+
|
7
|
+
- Add `performs` to help cut down Active Job boilerplate.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Post::Publisher < ActiveRecord::AssociatedObject
|
11
|
+
performs :publish, queue_as: :important
|
12
|
+
|
13
|
+
def publish
|
14
|
+
…
|
15
|
+
end
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
The above is the same as writing:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Post::Publisher < ActiveRecord::AssociatedObject
|
23
|
+
class Job < ApplicationJob; end
|
24
|
+
class PublishJob < Job
|
25
|
+
queue_as :important
|
26
|
+
|
27
|
+
def perform(publisher, *arguments, **options)
|
28
|
+
publisher.publish(*arguments, **options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def publish_later(*arguments, **options)
|
33
|
+
PublishJob.perform_later(self, *arguments, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def publish
|
37
|
+
…
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
See the README for more details.
|
43
|
+
|
1
44
|
## [0.2.0] - 2022-04-21
|
2
45
|
|
3
46
|
- Require a `has_object` call on the record side to associate an object.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active_record-associated_object (0.
|
4
|
+
active_record-associated_object (0.4.0)
|
5
|
+
active_job-performs
|
5
6
|
activerecord (>= 6.1)
|
6
7
|
|
7
8
|
GEM
|
@@ -20,6 +21,8 @@ GEM
|
|
20
21
|
erubi (~> 1.4)
|
21
22
|
rails-dom-testing (~> 2.0)
|
22
23
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
24
|
+
active_job-performs (0.1.1)
|
25
|
+
activejob (>= 6.1)
|
23
26
|
activejob (7.0.2.3)
|
24
27
|
activesupport (= 7.0.2.3)
|
25
28
|
globalid (>= 0.3.6)
|
@@ -55,10 +58,13 @@ GEM
|
|
55
58
|
nokogiri (>= 1.5.9)
|
56
59
|
method_source (1.0.0)
|
57
60
|
minitest (5.15.0)
|
61
|
+
minitest-sprint (1.2.2)
|
62
|
+
path_expander (~> 1.1)
|
58
63
|
nokogiri (1.13.4-arm64-darwin)
|
59
64
|
racc (~> 1.4)
|
60
65
|
nokogiri (1.13.4-x86_64-linux)
|
61
66
|
racc (~> 1.4)
|
67
|
+
path_expander (1.1.1)
|
62
68
|
racc (1.6.0)
|
63
69
|
rack (2.2.3)
|
64
70
|
rack-test (1.1.0)
|
@@ -95,6 +101,7 @@ DEPENDENCIES
|
|
95
101
|
debug
|
96
102
|
kredis
|
97
103
|
minitest (~> 5.0)
|
104
|
+
minitest-sprint (~> 1.2)
|
98
105
|
railties
|
99
106
|
rake (~> 13.0)
|
100
107
|
sqlite3
|
data/README.md
CHANGED
@@ -8,31 +8,91 @@ Associate a Ruby PORO with an Active Record class and have it quack like one. Bu
|
|
8
8
|
# app/models/post.rb
|
9
9
|
class Post < ActiveRecord::Base
|
10
10
|
# `has_object` defines a `publisher` method that calls Post::Publisher.new(post).
|
11
|
-
has_object :publisher
|
11
|
+
has_object :publisher
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
14
|
+
# app/models/post/publisher.rb
|
15
|
+
class Post::Publisher
|
16
|
+
def initialize(post)
|
17
|
+
@post = post
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
If you want Active Job, GlobalID and Kredis integration you can also have `Post::Publisher` inherit from `ActiveRecord::AssociatedObject`. This extends the standard PORO with details from the `Post::` namespace and the post primary key.
|
23
|
+
|
24
|
+
```ruby
|
15
25
|
# app/models/post/publisher.rb
|
16
26
|
class Post::Publisher < ActiveRecord::AssociatedObject
|
27
|
+
# ActiveRecord::AssociatedObject defines initialize(post) automatically. It's derived from the `Post::` namespace.
|
28
|
+
|
17
29
|
kredis_datetime :publish_at # Kredis integration generates a "post:publishers:<post_id>:publish_at" key.
|
18
30
|
|
19
|
-
#
|
20
|
-
|
31
|
+
# `performs` builds a `Post::Publisher::PublishJob` and routes configs over to it.
|
32
|
+
performs :publish, queue_as: :important, discard_on: SomeError do
|
33
|
+
retry_on TimeoutError, wait: :exponentially_longer
|
34
|
+
end
|
35
|
+
|
36
|
+
def publish
|
37
|
+
# `transaction` is syntactic sugar for `post.transaction` here.
|
21
38
|
transaction do
|
39
|
+
# A `post` method is generated to access the associated post. There's also a `record` alias available.
|
22
40
|
post.update! published: true
|
23
41
|
post.subscribers.post_published post
|
24
42
|
end
|
25
43
|
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
### How `performs` removes Active Job boilerplate
|
48
|
+
|
49
|
+
`performs` comes from the `active_job-performs` gem and is automatically bundled with `ActiveRecord::AssociatedObject`.
|
50
|
+
|
51
|
+
With an associated object like this:
|
26
52
|
|
27
|
-
|
28
|
-
|
53
|
+
```ruby
|
54
|
+
class Post::Publisher < ActiveRecord::AssociatedObject
|
55
|
+
performs queue_as: :important
|
56
|
+
performs :publish
|
57
|
+
performs :retract
|
58
|
+
|
59
|
+
def publish
|
60
|
+
end
|
61
|
+
|
62
|
+
def retract(reason:)
|
29
63
|
end
|
30
64
|
end
|
65
|
+
```
|
66
|
+
|
67
|
+
is equivalent to:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class Post::Publisher < ActiveRecord::AssociatedObject
|
71
|
+
# `performs` without a method defines a general job to share between method jobs.
|
72
|
+
class Job < ApplicationJob
|
73
|
+
queue_as :important
|
74
|
+
end
|
75
|
+
|
76
|
+
# Individual method jobs inherit from the `Post::Publisher::Job` defined above.
|
77
|
+
class PublishJob < Job
|
78
|
+
def perform(publisher, *arguments, **options)
|
79
|
+
# GlobalID integration means associated objects can be passed into jobs like Active Records, i.e. we don't have to do `post.publisher`.
|
80
|
+
publisher.publish(*arguments, **options)
|
81
|
+
end
|
82
|
+
end
|
31
83
|
|
32
|
-
class
|
33
|
-
|
34
|
-
|
35
|
-
|
84
|
+
class RetractJob < Job
|
85
|
+
def perform(publisher, *arguments, **options)
|
86
|
+
publisher.retract(*arguments, **options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def publish_later(*arguments, **options)
|
91
|
+
PublishJob.perform_later(self, *arguments, **options)
|
92
|
+
end
|
93
|
+
|
94
|
+
def retract_later(*arguments, **options)
|
95
|
+
RetractJob.perform_later(self, *arguments, **options)
|
36
96
|
end
|
37
97
|
end
|
38
98
|
```
|
@@ -44,7 +104,7 @@ end
|
|
44
104
|
```ruby
|
45
105
|
class Post < ActiveRecord::Base
|
46
106
|
# Callbacks can be passed too to a specific method.
|
47
|
-
has_object :publisher, after_touch: true, before_destroy: :
|
107
|
+
has_object :publisher, after_touch: true, before_destroy: :prevent_errant_post_destroy
|
48
108
|
|
49
109
|
# The above is the same as writing:
|
50
110
|
after_touch { publisher.after_touch }
|
@@ -58,11 +118,17 @@ class Post::Publisher < ActiveRecord::AssociatedObject
|
|
58
118
|
|
59
119
|
def prevent_errant_post_destroy
|
60
120
|
# Passed callbacks can throw :abort too, and in this example prevent post.destroy.
|
61
|
-
throw :abort
|
121
|
+
throw :abort if haha_business?
|
62
122
|
end
|
63
123
|
end
|
64
124
|
```
|
65
125
|
|
126
|
+
## Risks of depending on this gem
|
127
|
+
|
128
|
+
This gem is relatively tiny and I'm not expecting more significant changes on it, for right now. It's unofficial and not affiliated with Rails core.
|
129
|
+
|
130
|
+
Though it's written and maintained by an ex-Rails core person, so I know my way in and out of Rails and how to safely extend it.
|
131
|
+
|
66
132
|
## Installation
|
67
133
|
|
68
134
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -81,7 +147,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
81
147
|
|
82
148
|
## Contributing
|
83
149
|
|
84
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
150
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kaspth/active_record-associated_object.
|
85
151
|
|
86
152
|
## License
|
87
153
|
|
data/Rakefile
CHANGED
@@ -24,12 +24,11 @@ Gem::Specification.new do |spec|
|
|
24
24
|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
25
|
end
|
26
26
|
end
|
27
|
-
spec.bindir = "exe"
|
28
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
27
|
spec.require_paths = ["lib"]
|
30
28
|
|
31
29
|
# Uncomment to register a new dependency of your gem
|
32
30
|
spec.add_dependency "activerecord", ">= 6.1"
|
31
|
+
spec.add_dependency "active_job-performs"
|
33
32
|
|
34
33
|
# For more information and examples about making a new gem, check out our
|
35
34
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -1,17 +1,23 @@
|
|
1
1
|
module ActiveRecord::AssociatedObject::ObjectAssociation
|
2
|
+
using Module.new {
|
3
|
+
refine Module do
|
4
|
+
def extend_source_from(chunks, &block)
|
5
|
+
location = caller_locations(1, 1).first
|
6
|
+
source_chunks = Array(chunks).flat_map(&block)
|
7
|
+
class_eval source_chunks.join("\n\n"), location.path, location.lineno
|
8
|
+
end
|
9
|
+
end
|
10
|
+
}
|
11
|
+
|
2
12
|
def has_object(*names, **callbacks)
|
3
|
-
|
13
|
+
extend_source_from(names) do |name|
|
4
14
|
"def #{name}; @#{name} ||= #{self.name}::#{name.to_s.classify}.new(self); end"
|
5
15
|
end
|
6
16
|
|
7
|
-
|
8
|
-
|
9
|
-
passes = names.flat_map do |name|
|
17
|
+
extend_source_from(names) do |name|
|
10
18
|
callbacks.map do |callback, method|
|
11
19
|
"#{callback} { #{name}.#{method == true ? callback : method} }"
|
12
20
|
end
|
13
21
|
end
|
14
|
-
|
15
|
-
class_eval passes.join("\n\n"), __FILE__, __LINE__ + 1
|
16
22
|
end
|
17
23
|
end
|
@@ -2,12 +2,17 @@ class ActiveRecord::AssociatedObject::Railtie < Rails::Railtie
|
|
2
2
|
initializer "integrations.include" do
|
3
3
|
ActiveRecord::AssociatedObject.include Kredis::Attributes if defined?(Kredis)
|
4
4
|
ActiveRecord::AssociatedObject.include GlobalID::Identification if defined?(GlobalID)
|
5
|
+
|
6
|
+
ActiveSupport.on_load :active_job do
|
7
|
+
require "active_job/performs"
|
8
|
+
ActiveRecord::AssociatedObject.extend ActiveJob::Performs
|
9
|
+
end
|
5
10
|
end
|
6
11
|
|
7
12
|
initializer "object_association.setup" do
|
8
13
|
ActiveSupport.on_load :active_record do
|
9
14
|
require "active_record/associated_object/object_association"
|
10
|
-
|
15
|
+
extend ActiveRecord::AssociatedObject::ObjectAssociation
|
11
16
|
end
|
12
17
|
end
|
13
18
|
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.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kasper Timm Hansen
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: active_job-performs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
- hey@kaspth.com
|
@@ -64,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
78
|
- !ruby/object:Gem::Version
|
65
79
|
version: '0'
|
66
80
|
requirements: []
|
67
|
-
rubygems_version: 3.3.
|
81
|
+
rubygems_version: 3.3.21
|
68
82
|
signing_key:
|
69
83
|
specification_version: 4
|
70
84
|
summary: Associate a Ruby PORO with an Active Record class and have it quack like
|