Skip to content

Commit

Permalink
Add the ability to pass options to the has_many :versions association…
Browse files Browse the repository at this point in the history
… macro
  • Loading branch information
TylerRick committed Oct 29, 2018
1 parent c77ee68 commit 302fee1
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 16 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
36 changes: 31 additions & 5 deletions lib/paper_trail/model_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,35 @@ def setup_associations(options)
# In PR _____ () we ask them to use `paper_trail_options` instead.
@model_class.class_attribute :version_class_name
@model_class.version_class_name = options[:class_name] || "PaperTrail::Version"
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] || :versions
@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 - paper_trail_event
@model_class.send :attr_accessor, :paper_trail_event

assert_concrete_activerecord_class(@model_class.version_class_name)

# @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

Expand All @@ -185,5 +198,18 @@ def setup_options(options)

@model_class.paper_trail_options[:meta] ||= {}
end

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
8 changes: 8 additions & 0 deletions spec/dummy_app/app/models/concerns/versions_extensions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module VersionsExtensions
def inspect
"#{length} versions:\n" +
super
end
end
5 changes: 4 additions & 1 deletion spec/dummy_app/app/models/thing.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# frozen_string_literal: true

class Thing < ActiveRecord::Base
has_paper_trail
has_paper_trail versions: {
extend: VersionsExtensions,
scope: -> { order("id desc") }
}

if ActiveRecord.gem_version >= Gem::Version.new("5.0")
belongs_to :person, optional: true
Expand Down
19 changes: 19 additions & 0 deletions spec/models/thing_spec.rb
Original file line number Diff line number Diff line change
@@ -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 < VersionsExtensions
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
59 changes: 51 additions & 8 deletions spec/paper_trail/model_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,58 @@

module PaperTrail
::RSpec.describe ModelConfig do
describe "when has_paper_trail is called" do
it "raises an error" do
expect {
class MisconfiguredCVC < ActiveRecord::Base
has_paper_trail class_name: "AbstractVersion"
describe "has_paper_trail" do
describe "passing an abstract class to class_name" do
it "raises an error" do
expect {
Class.new(ActiveRecord::Base) do
has_paper_trail class_name: "AbstractVersion"
end
}.to raise_error(
/use concrete \(not abstract\) version models/
)
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
}.to raise_error(
/use concrete \(not abstract\) version models/
)
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
Expand Down

0 comments on commit 302fee1

Please sign in to comment.