Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Include ORM associations in CollectionDecorator #845

Merged
merged 2 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source "https://rubygems.org"
gemspec

platforms :ruby do
gem "sqlite3"
gem 'sqlite3', '~> 1.3.6'
end

platforms :jruby do
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,17 @@ your `ArticleDecorator` and they'll return decorated objects:
@article = ArticleDecorator.find(params[:id])
```

### Decorated Query Methods
By default, Draper will decorate all [QueryMethods](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html)
of ActiveRecord.
If you're using another ORM, in order to support it, you can tell Draper to use a custom strategy:

```ruby
Draper.configure do |config|
config.default_query_methods_strategy = :mongoid
end
```

### When to Decorate Objects

Decorators are supposed to behave very much like the models they decorate, and
Expand Down
1 change: 1 addition & 0 deletions lib/draper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
require 'draper/decorated_association'
require 'draper/helper_support'
require 'draper/view_context'
require 'draper/query_methods'
require 'draper/collection_decorator'
require 'draper/undecorate'
require 'draper/decorates_assigned'
Expand Down
1 change: 1 addition & 0 deletions lib/draper/collection_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Draper
class CollectionDecorator
include Enumerable
include Draper::ViewHelpers
include Draper::QueryMethods
extend Draper::Delegation

# @return the collection being decorated.
Expand Down
8 changes: 8 additions & 0 deletions lib/draper/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,13 @@ def default_controller
def default_controller=(controller)
@@default_controller = controller
end

def default_query_methods_strategy
@@default_query_methods_strategy ||= :active_record
end

def default_query_methods_strategy=(strategy)
@@default_query_methods_strategy = strategy
end
end
end
19 changes: 19 additions & 0 deletions lib/draper/query_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require_relative 'query_methods/load_strategy'

module Draper
module QueryMethods
# Proxies missing query methods to the source class if the strategy allows.
def method_missing(method, *args, &block)
return super unless strategy.allowed? method

object.send(method, *args, &block).decorate
end

private

# Configures the strategy used to proxy the query methods, which defaults to `:active_record`.
def strategy
@strategy ||= LoadStrategy.new(Draper.default_query_methods_strategy)
end
end
end
21 changes: 21 additions & 0 deletions lib/draper/query_methods/load_strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Draper
module QueryMethods
module LoadStrategy
def self.new(name)
const_get(name.to_s.camelize).new
end

class ActiveRecord
def allowed?(method)
::ActiveRecord::Relation::VALUE_METHODS.include? method
end
end

class Mongoid
def allowed?(method)
raise NotImplementedError
end
end
end
end
end
40 changes: 32 additions & 8 deletions spec/draper/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,44 @@ module Draper
Draper.configure { |config| expect(config).to be Draper }
end

it 'defaults default_controller to ApplicationController' do
expect(Draper.default_controller).to be ApplicationController
describe '#default_controller' do
it 'defaults default_controller to ApplicationController' do
expect(Draper.default_controller).to be ApplicationController
end

it 'allows customizing default_controller through configure' do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to write allows customizing default_controller through configuration?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even, through Draper#configure, through configure method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure 🤔. I've just moved this old tests into a context, maybe these changes are out the scope of this pr?

default = Draper.default_controller

Draper.configure do |config|
config.default_controller = CustomController
end

expect(Draper.default_controller).to be CustomController

Draper.default_controller = default
end
end

it 'allows customizing default_controller through configure' do
default = Draper.default_controller
describe '#default_query_methods_strategy' do
let!(:default) { Draper.default_query_methods_strategy }

subject { Draper.default_query_methods_strategy }

Draper.configure do |config|
config.default_controller = CustomController
context 'when there is no custom strategy' do
it { is_expected.to eq(:active_record) }
end

expect(Draper.default_controller).to be CustomController
context 'when using a custom strategy' do
before do
Draper.configure do |config|
config.default_query_methods_strategy = :mongoid
end
end

Draper.default_controller = default
after { Draper.default_query_methods_strategy = default }

it { is_expected.to eq(:mongoid) }
end
end
end
end
26 changes: 26 additions & 0 deletions spec/draper/query_methods/load_strategy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'spec_helper'
require 'active_record'

module Draper
module QueryMethods
describe LoadStrategy do
describe '#new' do
subject { described_class.new(:active_record) }

it { is_expected.to be_an_instance_of(LoadStrategy::ActiveRecord) }
end
end

describe LoadStrategy::ActiveRecord do
describe '#allowed?' do
it 'checks whether or not ActiveRecord::Relation::VALUE_METHODS has the given method' do
allow(::ActiveRecord::Relation::VALUE_METHODS).to receive(:include?)

described_class.new.allowed? :foo

expect(::ActiveRecord::Relation::VALUE_METHODS).to have_received(:include?).with(:foo)
end
end
end
end
end
39 changes: 39 additions & 0 deletions spec/draper/query_methods_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'spec_helper'
require_relative '../dummy/app/decorators/post_decorator'

Post = Struct.new(:id) { }

module Draper
describe QueryMethods do
describe '#method_missing' do
let(:collection) { [ Post.new, Post.new ] }
let(:collection_decorator) { PostDecorator.decorate_collection(collection) }
let(:fake_strategy) { instance_double(QueryMethods::LoadStrategy::ActiveRecord) }

before { allow(QueryMethods::LoadStrategy).to receive(:new).and_return(fake_strategy) }

context 'when strategy allows collection to call the method' do
let(:results) { spy(:results) }

before do
allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(true)
allow(collection).to receive(:send).with(:some_query_method).and_return(results)
end

it 'calls the method on the collection and decorate it results' do
collection_decorator.some_query_method

expect(results).to have_received(:decorate)
end
end

context 'when strategy does not allow collection to call the method' do
before { allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(false) }

it 'raises NoMethodError' do
expect { collection_decorator.some_query_method }.to raise_exception(NoMethodError)
end
end
end
end
end