Skip to content

Commit

Permalink
Merge pull request #106 from hugopeixoto/feature/pass-a-block-to-an-a…
Browse files Browse the repository at this point in the history
…ssociation

Allow associations to be defined with a block
  • Loading branch information
philipqnguyen authored Oct 17, 2018
2 parents 5e1ec8e + 5073953 commit 32804d7
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 16 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,44 @@ Output:
}
```

#### Defining an association directly in the Blueprint

You can also pass a block to an association:

```ruby
class ProjectBlueprint < Blueprinter::Base
identifier :uuid
field :name
end

class UserBlueprint < Blueprinter::Base
identifier :uuid

association :projects, blueprint: ProjectBlueprint do |user|
user.projects + user.company.projects
end
end
```

Usage:

```ruby
puts UserBlueprint.render(user)
```

Output:

```json
{
"uuid": "733f0758-8f21-4719-875f-262c3ec743af",
"projects": [
{"uuid": "b426a1e6-ac41-45ab-bfef-970b9a0b4289", "name": "query-console"},
{"uuid": "5bd84d6c-4fd2-4e36-ae31-c137e39be542", "name": "blueprinter"},
{"uuid": "785f5cd4-7d8d-4779-a6dd-ec5eab440eff", "name": "uncontrollable"}
]
}
```

### Passing additional properties to `render`

`render` takes an options hash which you can pass additional properties, allowing you to utilize those additional properties in the `field` block. For example:
Expand Down
34 changes: 25 additions & 9 deletions lib/blueprinter/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Base
# end
#
# @return [Field] A Field object
def self.identifier(method, name: method, extractor: AutoExtractor)
def self.identifier(method, name: method, extractor: AutoExtractor.new)
view_collection[:identifier] << Field.new(method, name, extractor, self)
end

Expand Down Expand Up @@ -97,9 +97,9 @@ def self.inherited(subclass)
# @return [Field] A Field object
def self.field(method, options = {}, &block)
options = if block_given?
{name: method, extractor: BlockExtractor, block: {method => block}}
{name: method, extractor: BlockExtractor.new, block: block}
else
{name: method, extractor: AutoExtractor}
{name: method, extractor: AutoExtractor.new}
end.merge(options)
current_view << Field.new(method,
options[:name],
Expand All @@ -119,6 +119,8 @@ def self.field(method, options = {}, &block)
# JSON output.
# @option options [Symbol] :view Specify the view to use or fall back to
# to the :default view.
# @yield [Object] The object passed to `render` is also passed to the
# block.
#
# @example Specifying an association
# class UserBlueprint < Blueprinter::Base
Expand All @@ -127,15 +129,29 @@ def self.field(method, options = {}, &block)
# # code
# end
#
# @example Passing a block to be evaluated as the value.
# class UserBlueprint < Blueprinter::Base
# association :vehicles, blueprint: VehiclesBlueprint do |user|
# user.vehicles + user.company.vehicles
# end
# end
#
# @return [Field] A Field object
def self.association(method, options = {})
def self.association(method, options = {}, &block)
raise BlueprinterError, 'blueprint required' unless options[:blueprint]
name = options.delete(:name) || method

options = if block_given?
options.merge(extractor: BlockExtractor.new, block: block)
else
options.merge(extractor: AutoExtractor.new)
end

current_view << Field.new(method,
name,
AssociationExtractor,
self,
options.merge(association: true))
name,
AssociationExtractor.new,
self,
options.merge(association: true))
end

# Generates a JSON formatted String.
Expand Down Expand Up @@ -220,7 +236,7 @@ def self.prepare(object, view_name:, local_options:)
# @return [Array<Symbol>] an array of field names
def self.fields(*field_names)
field_names.each do |field_name|
current_view << Field.new(field_name, field_name, AutoExtractor, self)
current_view << Field.new(field_name, field_name, AutoExtractor.new, self)
end
end

Expand Down
3 changes: 0 additions & 3 deletions lib/blueprinter/extractor.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# @api private
module Blueprinter
class Extractor
def initialize
end

def extract(field_name, object, local_options, options={})
fail NotImplementedError, "An Extractor must implement #extract"
end
Expand Down
4 changes: 2 additions & 2 deletions lib/blueprinter/extractors/association_extractor.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module Blueprinter
class AssociationExtractor < Extractor
def extract(association_name, object, local_options, options={})
value = object.public_send(association_name)
return (value || options[:default]) if value.nil?
value = options[:extractor].extract(association_name, object, local_options, options)
return options[:default] if value.nil?
view = options[:view] || :default
options[:blueprint].prepare(value, view_name: view, local_options: local_options)
end
Expand Down
7 changes: 6 additions & 1 deletion lib/blueprinter/extractors/auto_extractor.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
module Blueprinter
class AutoExtractor < Extractor
def initialize
@hash_extractor = HashExtractor.new
@public_send_extractor = PublicSendExtractor.new
end

def extract(field_name, object, local_options, options = {})
extractor = object.is_a?(Hash) ? HashExtractor : PublicSendExtractor
extractor = object.is_a?(Hash) ? @hash_extractor : @public_send_extractor
extraction = extractor.extract(field_name, object, local_options, options)
options.key?(:datetime_format) ? format_datetime(extraction, options[:datetime_format]) : extraction
end
Expand Down
2 changes: 1 addition & 1 deletion lib/blueprinter/extractors/block_extractor.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Blueprinter
class BlockExtractor < Extractor
def extract(field_name, object, local_options, options = {})
options[:block][field_name].call(object, local_options)
options[:block].call(object, local_options)
end
end
end
16 changes: 16 additions & 0 deletions spec/integrations/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@
end
it('returns json with association') { should eq(result) }
end
context 'Given block is passed' do
let(:blueprint) do
vehicle_blueprint = Class.new(Blueprinter::Base) do
fields :make
end

Class.new(Blueprinter::Base) do
identifier :id
association(:automobiles, blueprint: vehicle_blueprint) { |o| o.vehicles }
end
end
let(:result) do
'{"id":' + obj_id + ',"automobiles":[{"make":"Super Car"}]}'
end
it('returns json with aliased association') { should eq(result) }
end
context 'Given no associated blueprint is given' do
let(:blueprint) do
Class.new(Blueprinter::Base) do
Expand Down

0 comments on commit 32804d7

Please sign in to comment.