diff --git a/README.md b/README.md index 197e630c..e600b1a1 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,18 @@ Output: ### Associations You may include associated objects. Say for example, a user has projects: ```ruby +class ProjectBlueprint < Blueprinter::Base + identifier :uuid + field :name +end + class UserBlueprint < Blueprinter::Base identifier :uuid field :email, name: :login view :normal do fields :first_name, :last_name - association :projects + association :projects, blueprint: ProjectBlueprint end end ``` diff --git a/lib/blueprinter/base.rb b/lib/blueprinter/base.rb index 89b528b2..6a4f4cd1 100644 --- a/lib/blueprinter/base.rb +++ b/lib/blueprinter/base.rb @@ -87,6 +87,8 @@ def self.field(method, options = {}, &block) # # @param method [Symbol] the association name # @param options [Hash] options to overide defaults. + # @option options [Symbol] :blueprint Required. Use this to specify the + # blueprint to use for the associated object. # @option options [Symbol] :name Use this to rename the association in the # JSON output. # @option options [Symbol] :view Specify the view to use or fall back to @@ -95,12 +97,13 @@ def self.field(method, options = {}, &block) # @example Specifying an association # class UserBlueprint < Blueprinter::Base # # code - # association :vehicles, view: :extended + # association :vehicles, view: :extended, blueprint: VehiclesBlueprint # # code # end # # @return [Field] A Field object def self.association(method, options = {}) + raise BlueprinterError, 'blueprint required' unless options[:blueprint] name = options.delete(:name) || method current_view << Field.new(method, name, diff --git a/lib/blueprinter/serializers/association_serializer.rb b/lib/blueprinter/serializers/association_serializer.rb index 5528a523..9ac1d772 100644 --- a/lib/blueprinter/serializers/association_serializer.rb +++ b/lib/blueprinter/serializers/association_serializer.rb @@ -1,10 +1,8 @@ class Blueprinter::AssociationSerializer < Blueprinter::Serializer def serialize(association_name, object, local_options, options={}) - if options[:blueprint] - view = options[:view] || :default - options[:blueprint].prepare(object.public_send(association_name), view_name: view, local_options: local_options) - else - object.public_send(association_name) - end + value = object.public_send(association_name) + return value if value.nil? + view = options[:view] || :default + options[:blueprint].prepare(value, view_name: view, local_options: local_options) end end diff --git a/spec/integrations/base_spec.rb b/spec/integrations/base_spec.rb index 526e8de2..76adbcbb 100644 --- a/spec/integrations/base_spec.rb +++ b/spec/integrations/base_spec.rb @@ -67,6 +67,40 @@ let(:vehicle) { create(:vehicle) } include_examples 'Base::render' + + context 'Given blueprint has ::association' do + let(:result) do + '{"id":' + obj_id + ',"vehicles":[{"make":"Super Car"}]}' + end + let(:blueprint_without_associated_blueprint) do + Class.new(Blueprinter::Base) do + identifier :id + association :vehicles + end + end + before { vehicle.update(user: obj) } + context 'Given associated blueprint is given' do + let(:blueprint) do + vehicle_blueprint = Class.new(Blueprinter::Base) do + fields :make + end + Class.new(Blueprinter::Base) do + identifier :id + association :vehicles, blueprint: vehicle_blueprint + end + end + it('returns json with association') { should eq(result) } + end + context 'Given no associated blueprint is given' do + let(:blueprint) do + Class.new(Blueprinter::Base) do + identifier :id + association :vehicles + end + end + it { expect{subject}.to raise_error(Blueprinter::BlueprinterError) } + end + end end end end