Add the ability to pass any options to the has_many :versions association macro
This commit is contained in:
parent
3f93befd88
commit
337e1a90f2
|
@ -11,7 +11,8 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
|
|||
|
||||
### Added
|
||||
|
||||
- None
|
||||
- [#1158](https://github.com/paper-trail-gem/paper_trail/pull/1158) — Add the ability to pass
|
||||
options, such as `scope` or `extend:` to the `has_many :versions` association macro.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
19
README.md
19
README.md
|
@ -955,7 +955,7 @@ see https://github.com/paper-trail-gem/paper_trail/issues/594
|
|||
### 5.b. Configuring the `versions` Association
|
||||
|
||||
You may configure the name of the `versions` association by passing
|
||||
a different name to `has_paper_trail`.
|
||||
a different name to `has_paper_trail`:
|
||||
|
||||
```ruby
|
||||
class Post < ActiveRecord::Base
|
||||
|
@ -968,6 +968,23 @@ Post.new.versions # => NoMethodError
|
|||
Overriding (instead of configuring) the `versions` method is not supported.
|
||||
Overriding associations is not recommended in general.
|
||||
|
||||
You may pass other options for the `has_many` by passing a hash of options:
|
||||
|
||||
```ruby
|
||||
class Post < ActiveRecord::Base
|
||||
has_paper_trail versions: {
|
||||
name: :drafts,
|
||||
scope: -> { order("id desc") },
|
||||
extend: VersionsExtensions
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
Refer to
|
||||
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many-label-Options
|
||||
for the full list of supported options for `has_many`.
|
||||
|
||||
|
||||
### 5.c. Generators
|
||||
|
||||
PaperTrail has one generator, `paper_trail:install`. It writes, but does not
|
||||
|
|
|
@ -142,21 +142,35 @@ module PaperTrail
|
|||
@model_class.class_attribute :version_class_name
|
||||
@model_class.version_class_name = options[:class_name] || "PaperTrail::Version"
|
||||
|
||||
# @api private - versions_association_name
|
||||
@model_class.class_attribute :versions_association_name
|
||||
@model_class.versions_association_name = options[:versions] || :versions
|
||||
assert_concrete_activerecord_class(@model_class.version_class_name)
|
||||
|
||||
# @api public - paper_trail_event
|
||||
@model_class.send :attr_accessor, :paper_trail_event
|
||||
|
||||
assert_concrete_activerecord_class(@model_class.version_class_name)
|
||||
setup_has_many_versions(options)
|
||||
end
|
||||
|
||||
def setup_has_many_versions(options)
|
||||
unless options[:versions].is_a?(Hash)
|
||||
options[:versions] = {
|
||||
name: options[:versions]
|
||||
}
|
||||
end
|
||||
|
||||
# @api private - versions_association_name
|
||||
@model_class.class_attribute :versions_association_name
|
||||
@model_class.versions_association_name = options[:versions].delete(:name) || :versions
|
||||
|
||||
scope = options[:versions].delete(:scope) || -> { order(model.timestamp_sort_order) }
|
||||
options[:versions].assert_valid_keys(valid_keys_for_has_many)
|
||||
|
||||
# @api public
|
||||
@model_class.has_many(
|
||||
@model_class.versions_association_name,
|
||||
-> { order(model.timestamp_sort_order) },
|
||||
scope,
|
||||
class_name: @model_class.version_class_name,
|
||||
as: :item
|
||||
as: :item,
|
||||
**options[:versions]
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -182,5 +196,19 @@ module PaperTrail
|
|||
|
||||
@model_class.paper_trail_options[:meta] ||= {}
|
||||
end
|
||||
|
||||
# @api private
|
||||
def valid_keys_for_has_many
|
||||
if ActiveRecord.gem_version >= Gem::Version.new("5.0")
|
||||
ActiveRecord::Associations::Builder::HasMany.valid_options({})
|
||||
else
|
||||
%i[
|
||||
after_add after_remove anonymous_class as autosave before_add
|
||||
before_remove class_name counter_cache dependent extend foreign_key
|
||||
foreign_type inverse_of join_table primary_key source source_type
|
||||
table_name through validate
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PrefixVersionsInspectWithCount
|
||||
def inspect
|
||||
"#{length} versions:\n" +
|
||||
super
|
||||
end
|
||||
end
|
|
@ -1,7 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Thing < ActiveRecord::Base
|
||||
has_paper_trail
|
||||
has_paper_trail versions: {
|
||||
extend: PrefixVersionsInspectWithCount,
|
||||
scope: -> { order("id desc") }
|
||||
}
|
||||
|
||||
if ActiveRecord.gem_version >= Gem::Version.new("5.0")
|
||||
belongs_to :person, optional: true
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Thing, type: :model do
|
||||
describe "#versions", versioning: true do
|
||||
let(:thing) { Thing.create! }
|
||||
|
||||
it "applies the extend option" do
|
||||
expect(thing.versions.singleton_class).to be < PrefixVersionsInspectWithCount
|
||||
expect(thing.versions.inspect).to start_with("1 versions:")
|
||||
end
|
||||
|
||||
it "applies the scope option" do
|
||||
expect(Thing.reflect_on_association(:versions).scope).to be_a Proc
|
||||
expect(thing.versions.to_sql).to end_with "ORDER BY id desc"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,10 +4,11 @@ require "spec_helper"
|
|||
|
||||
module PaperTrail
|
||||
::RSpec.describe ModelConfig do
|
||||
describe "when has_paper_trail is called" do
|
||||
describe "has_paper_trail" do
|
||||
describe "passing an abstract class to class_name" do
|
||||
it "raises an error" do
|
||||
expect {
|
||||
class MisconfiguredCVC < ActiveRecord::Base
|
||||
Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail class_name: "AbstractVersion"
|
||||
end
|
||||
}.to raise_error(
|
||||
|
@ -15,5 +16,47 @@ module PaperTrail
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "versions:" do
|
||||
it "name can be passed instead of an options hash" do
|
||||
klass = Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail versions: :drafts
|
||||
end
|
||||
expect(klass.reflect_on_association(:drafts)).to be_a(
|
||||
ActiveRecord::Reflection::HasManyReflection
|
||||
)
|
||||
end
|
||||
it "name can be passed in the options hash" do
|
||||
klass = Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail versions: { name: :drafts }
|
||||
end
|
||||
expect(klass.reflect_on_association(:drafts)).to be_a(
|
||||
ActiveRecord::Reflection::HasManyReflection
|
||||
)
|
||||
end
|
||||
it "allows any option that has_many supports" do
|
||||
klass = Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail versions: { autosave: true, validate: true }
|
||||
end
|
||||
expect(klass.reflect_on_association(:versions).options[:autosave]).to eq true
|
||||
expect(klass.reflect_on_association(:versions).options[:validate]).to eq true
|
||||
end
|
||||
it "can even override options that PaperTrail adds to has_many" do
|
||||
klass = Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail versions: { as: :foo }
|
||||
end
|
||||
expect(klass.reflect_on_association(:versions).options[:as]).to eq :foo
|
||||
end
|
||||
it "raises an error on unknown has_many options" do
|
||||
expect {
|
||||
Class.new(ActiveRecord::Base) do
|
||||
has_paper_trail versions: { read_my_mind: true, validate: true }
|
||||
end
|
||||
}.to raise_error(
|
||||
/Unknown key: :read_my_mind. Valid keys are: .*:class_name,/
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue