active_record-associated_object 0.2.0 → 0.4.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: 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