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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7a508d24fc9467600956a207ee83775b3618ccfc8be21a9c11e1caa72a11172
4
- data.tar.gz: '0360292507cf0e901140b9fe4003bd33aca10aff9d91b01b759e4ce75497a61f'
3
+ metadata.gz: 20d91687ec83cbc70efb318377c15ce1e2e79b28d350322b0d91ad6618073cc2
4
+ data.tar.gz: 59f19eea143da23bd980f0d6bca5a96670e13c7859d0db860741bf314d949822
5
5
  SHA512:
6
- metadata.gz: '04940ddec3fc5446ba945df9f3daeff22550e81cf55f1e61e02b79af687adfc376f05e1148a6fb0e7ff17ab5df1a4539fe1d1c8bde456b986561df8ef4714e93'
7
- data.tar.gz: 49ff34bb1945649479481ef7502585a124ee1464c67bc29f8ccb3335a53cfd0858790fbeebb976e3dfa1f055d12c085bb26ab2a7d054bed2ef51420f40cf83d5
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
@@ -15,3 +15,5 @@ gem "sqlite3"
15
15
  gem "kredis"
16
16
  gem "activejob"
17
17
  gem "railties"
18
+
19
+ gem "minitest-sprint", "~> 1.2"
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- active_record-associated_object (0.2.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, after_touch: true, before_destroy: :prevent_post_destroy
11
+ has_object :publisher
12
12
  end
13
13
 
14
- # Create a standard PORO, but derive attributes from the Post:: namespace and its primary key.
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
- # Both a general `record` method and a `post` method is available to access the associated post.
20
- def publish_now
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
- def publish_later
28
- PublishJob.set(wait_until: publish_at).perform_later self
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 Post::Publisher::PublishJob < ActiveJob::Base
33
- def perform(publisher)
34
- # Automatic integration via GlobalID means you don't have to do `post.publisher`.
35
- publisher.publish_now
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: :prevent_post_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 unless haha_business?
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/[USERNAME]/active_record-associated_object.
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
@@ -1,12 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
10
- end
11
-
12
- task default: :test
@@ -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
- methods = names.map do |name|
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
- class_eval methods.join("\n\n"), __FILE__, __LINE__ + 1
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
- ActiveRecord::Base.extend ActiveRecord::AssociatedObject::ObjectAssociation
15
+ extend ActiveRecord::AssociatedObject::ObjectAssociation
11
16
  end
12
17
  end
13
18
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class AssociatedObject
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-21 00:00:00.000000000 Z
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.11
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