require 'spec_helper' shared_examples 'nested attributes' do before do stub_model :project do include Granite::Form::Model::Primary include Granite::Form::Model::Associations primary :slug, String attribute :title, String end stub_model :profile do include Granite::Form::Model::Primary include Granite::Form::Model::Associations primary :identifier attribute :first_name, String end end context 'embeds_one' do let(:user) { User.new } specify { expect { user.profile_attributes = {} }.to change { user.profile }.to(an_instance_of(Profile)) } specify { expect { user.profile_attributes = {first_name: 'User'} }.to change { user.profile.try(:first_name) }.to('User') } specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User'} }.to raise_error Granite::Form::ObjectNotFound } context ':reject_if' do context do before { User.accepts_nested_attributes_for :profile, reject_if: :all_blank } specify { expect { user.profile_attributes = {first_name: ''} }.not_to change { user.profile } } end context do before { User.accepts_nested_attributes_for :profile, reject_if: ->(attributes) { attributes['first_name'].blank? } } specify { expect { user.profile_attributes = {first_name: ''} }.not_to change { user.profile } } end end context 'existing' do let(:profile) { Profile.new(first_name: 'User') } let(:user) { User.new profile: profile } specify { expect { user.profile_attributes = {identifier: 42, first_name: 'User'} }.to raise_error Granite::Form::ObjectNotFound } specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1'} }.to change { user.profile.first_name }.to('User 1') } specify { expect { user.profile_attributes = {first_name: 'User 1'} }.to change { user.profile.first_name }.to('User 1') } specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'} }.not_to change { user.profile.first_name } } specify do expect do user.profile_attributes = {first_name: 'User 1', _destroy: '1'} end.not_to change { user.profile.first_name } end specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} }.to change { user.profile.first_name }.to('User 1') } specify do expect do user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} end.to change { user.profile.first_name }.to('User 1') end context ':allow_destroy' do before { User.accepts_nested_attributes_for :profile, allow_destroy: true } specify { expect { user.profile_attributes = {first_name: 'User 1', _destroy: '1'} }.not_to change { user.profile.first_name } } specify do expect do user.profile_attributes = {first_name: 'User 1', _destroy: '1'} end.not_to change { user.profile.first_name } end specify { expect { user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} }.to change { user.profile }.to(nil) } specify do expect do user.profile_attributes = {identifier: profile.identifier.to_s, first_name: 'User 1', _destroy: '1'} end.to change { user.profile }.to(nil) end end context ':update_only' do before { User.accepts_nested_attributes_for :profile, update_only: true } specify do expect { user.profile_attributes = {identifier: 42, first_name: 'User 1'} } .to change { user.profile.first_name }.to('User 1') end end end context 'not primary' do before do stub_model :profile do attribute :identifier, Integer attribute :first_name, String end end specify { expect { user.profile_attributes = {} }.to change { user.profile }.to(an_instance_of(Profile)) } specify { expect { user.profile_attributes = {first_name: 'User'} }.to change { user.profile.try(:first_name) }.to('User') } context do let(:profile) { Profile.new(first_name: 'User') } let(:user) { User.new profile: profile } specify do expect { user.profile_attributes = {identifier: 42, first_name: 'User 1'} } .to change { user.profile.first_name }.to('User 1') end end end context 'generated method overwrites' do before do User.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def profile_attributes=(args) args.reverse_merge!(first_name: 'Default Profile Name') super end RUBY end it 'allows generated method overwritting' do expect { user.profile_attributes = {} } .to change { user.profile.try(:first_name) }.to('Default Profile Name') end end end context 'embeds_many' do let(:user) { User.new } specify { expect { user.projects_attributes = {} }.not_to change { user.projects } } specify do expect { user.projects_attributes = [{title: 'Project 1'}, {title: 'Project 2'}] } .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) end specify do expect { user.projects_attributes = {1 => {title: 'Project 1'}, 2 => {title: 'Project 2'}} } .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) end specify do expect { user.projects_attributes = [{slug: 42, title: 'Project 1'}, {title: 'Project 2'}] } .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 2']) end specify do expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] } .to change { user.projects.map(&:title) }.to(['', 'Project 2']) end context ':limit' do before { User.accepts_nested_attributes_for :projects, limit: 1 } specify do expect { user.projects_attributes = [{title: 'Project 1'}] } .to change { user.projects.map(&:title) }.to(['Project 1']) end specify do expect { user.projects_attributes = [{title: 'Project 1'}, {title: 'Project 2'}] } .to raise_error Granite::Form::TooManyObjects end end context ':reject_if' do context do before { User.accepts_nested_attributes_for :projects, reject_if: :all_blank } specify do expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] } .to change { user.projects.map(&:title) }.to(['Project 2']) end end context do before { User.accepts_nested_attributes_for :projects, reject_if: ->(attributes) { attributes['title'].blank? } } specify do expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] } .to change { user.projects.map(&:title) }.to(['Project 2']) end end context do before { User.accepts_nested_attributes_for :projects, reject_if: ->(attributes) { attributes['foobar'].blank? } } specify do expect { user.projects_attributes = [{title: ''}, {title: 'Project 2'}] } .not_to change { user.projects } end end end context 'existing' do let(:projects) { Array.new(2) { |i| Project.new(title: "Project #{i.next}").tap { |pr| pr.slug = 42 + i } } } let(:user) { User.new projects: projects } specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3'}, {title: 'Project 4'} ] end .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2', 'Project 4']) end specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3'}, {slug: 33, title: 'Project 4'} ] end .to change { user.projects.map(&:slug) }.to(%w[42 43 33]) end specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3'}, {slug: 33, title: 'Project 4', _destroy: 1} ] end .not_to change { user.projects.map(&:slug) } end specify do expect do user.projects_attributes = { 1 => {slug: projects.first.slug.to_i, title: 'Project 3'}, 2 => {title: 'Project 4'} } end .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2', 'Project 4']) end specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'}, {title: 'Project 4', _destroy: '1'} ] end .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) end specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'}, {title: 'Project 4', _destroy: '1'} ] end .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) end context ':allow_destroy' do before { User.accepts_nested_attributes_for :projects, allow_destroy: true } specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3', _destroy: '1'}, {title: 'Project 4', _destroy: '1'} ] end .to change { user.projects.map(&:title) }.to(['Project 2']) end end context ':update_only' do before { User.accepts_nested_attributes_for :projects, update_only: true } specify do expect do user.projects_attributes = [ {slug: projects.first.slug.to_i, title: 'Project 3'}, {title: 'Project 4'} ] end .to change { user.projects.map(&:title) }.to(['Project 3', 'Project 2']) end specify do expect do user.projects_attributes = [ {slug: projects.last.slug.to_i, title: 'Project 3'}, {slug: projects.first.slug.to_i.pred, title: 'Project 0'} ] end .to change { user.projects.map(&:title) }.to(['Project 1', 'Project 3']) end end end context 'primary absence causes exception' do before do stub_model :project do include Granite::Form::Model::Primary attribute :slug, String attribute :title, String end end specify { expect { user.projects_attributes = {} }.to raise_error Granite::Form::UndefinedPrimaryAttribute } end context 'generated method overwrites' do before do User.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def projects_attributes=(args) args << {title: 'Default Project'} super end RUBY end it 'allows generated method overwritting' do expect { user.projects_attributes = [] } .to change { user.projects.map(&:title) }.to(['Default Project']) end end end end