spec/attributes_spec.rb in cistern-2.4.1 vs spec/attributes_spec.rb in cistern-2.5.0

- old
+ new

@@ -1,166 +1,260 @@ require 'spec_helper' -describe Cistern::Attributes, 'requires' do - class RequireSpec < Sample::Model - identity :id - attribute :name, type: :string - attribute :type +describe Cistern::Attributes, '#request_attributes' do + subject { Class.new(Sample::Model) } + + it 'returns a reverse-aliased attributes hash' do + subject.class_eval do + identity :id + attribute :name, alias: 'sample_name' + end + + model = subject.new(name: 'steve', id: 1) + + expect(model.request_attributes).to eq( + 'sample_name' => 'steve', + 'id' => 1 + ) end + it 'drops duplicates values for multiple aliases' do + subject.class_eval do + identity :id + attribute :name, aliases: ['sample_name', 'other_name'] + end + + model = subject.new(name: 'steve', id: 1) + + expect(model.request_attributes).to eq( + 'sample_name' => 'steve', + 'other_name' => 'steve', + 'id' => 1 + ) + end +end + +describe Cistern::Attributes, '#dirty_request_attributes' do + subject { Class.new(Sample::Model) } + + it 'returns a reverse-aliased attributes hash of dirty attributes only' do + subject.class_eval do + identity :id + attribute :name, alias: 'sample_name' + end + + model = subject.new + model.merge_attributes(name: 'steve', id: 1) + + model.name = 'bob' + + expect(model.dirty_request_attributes).to eq( + 'sample_name' => 'bob', + ) + end + + it 'drops duplicates values for multiple aliases' do + subject.class_eval do + identity :id + attribute :name, aliases: ['sample_name', 'other_name'] + end + + model = subject.new(name: 'steve', id: 1) + + expect(model.request_attributes).to eq( + 'sample_name' => 'steve', + 'other_name' => 'steve', + 'id' => 1 + ) + end +end + +describe Cistern::Attributes, 'requires' do + subject { + Class.new(Sample::Model) do + identity :id + attribute :name, type: :string + attribute :type + end + } + it 'raises if required attributes are not present' do expect { - Sample.new.require_spec.requires :name + subject.new.requires :name }.to raise_exception(ArgumentError, /name is required/) data = { name: '1' } - return_value = Sample.new.require_spec(data).requires :name + return_value = subject.new(data).requires :name expect(return_value).to eq(data) expect { - Sample.new.require_spec.requires :name, :type + subject.new.requires :name, :type }.to raise_exception(ArgumentError, /name and type are required/) data = { name: '1', type: 'sample' } - return_values = Sample.new.require_spec(data).requires :name, :type + return_values = subject.new(data).requires :name, :type expect(return_values).to eq(data) end it 'raises if a required attribute attribute is not present' do expect { - Sample.new.require_spec.requires_one :name, :type + subject.new.requires_one :name, :type }.to raise_exception(ArgumentError, /name or type are required/) data = { name: '1' } - return_value = Sample.new.require_spec(data).requires_one :name, :type + return_value = subject.new(data).requires_one :name, :type expect(return_value).to eq(data) data = { name: '1', type: 'sample' } - return_values = Sample.new.require_spec(data).requires_one :name, :type + return_values = subject.new(data).requires_one :name, :type expect(return_values).to eq(data) end end describe Cistern::Attributes, 'parsing' do - class TypeSpec < Sample::Model - identity :id - attribute :name, type: :string - attribute :created_at, type: :time - attribute :flag, type: :boolean - attribute :list, type: :array - attribute :number, type: :integer - attribute :floater, type: :float - attribute :butternut_id, squash: %w(squash id), type: :integer - attribute :butternut_type, squash: %w(squash type) - attribute :squash - attribute :vegetable, aliases: 'squash' - attribute :custom, parser: lambda { |v, _| "X!#{v}" } - attribute :default, default: 'im a squash' - attribute :string_allow_nil, type: :string, allow_nil: true + subject { + Class.new(Sample::Model) + } - attribute :same_alias_1, aliases: 'nested' - attribute :same_alias_2, aliases: 'nested' - - attribute :same_alias_squashed_1, squash: %w(nested attr_1) - attribute :same_alias_squashed_2, squash: %w(nested attr_2) - attribute :same_alias_squashed_3, squash: %w(nested attr_2) - attribute :adam_attributes, aliases: 'attributes' - - def save - requires :flag + it 'should parse string' do + subject.class_eval do + attribute :name, type: :string end - end - it 'should parse string' do - expect(TypeSpec.new(name: 1).name).to eq('1') - expect(TypeSpec.new(name: "b").name).to eq('b') - expect(TypeSpec.new(name: nil).name).to eq(nil) + expect(subject.new(name: 1).name).to eq('1') + expect(subject.new(name: "b").name).to eq('b') + expect(subject.new(name: nil).name).to eq(nil) end - it 'should allow nils in string types' do - expect(TypeSpec.new(string_allow_nil: nil).string_allow_nil).to eq(nil) - end it "should handle a 'attributes' aliased attribute" do - expect(TypeSpec.new(attributes: 'x').adam_attributes).to eq('x') + subject.class_eval do + attribute :adam_attributes, aliases: 'attributes' + end + expect(subject.new(attributes: 'x').adam_attributes).to eq('x') end it 'should parse time' do + subject.class_eval do + attribute :created_at, type: :time + end + time = Time.now - created_at = TypeSpec.new(created_at: time.to_s).created_at + created_at = subject.new(created_at: time.to_s).created_at expect(created_at).to be_a(Time) expect(created_at.to_i).to eq(time.to_i) end it 'should parse boolean' do - expect(TypeSpec.new(flag: 'false').flag).to be_falsey - expect(TypeSpec.new(flag: 'true').flag).to be_truthy - expect(TypeSpec.new(flag: false).flag).to be_falsey - expect(TypeSpec.new(flag: true).flag).to be_truthy - expect(TypeSpec.new(flag: '0').flag).to be_falsey - expect(TypeSpec.new(flag: '1').flag).to be_truthy - expect(TypeSpec.new(flag: 0).flag).to be_falsey - expect(TypeSpec.new(flag: 1).flag).to be_truthy - expect(TypeSpec.new(flag: false)).not_to be_flag - expect(TypeSpec.new(flag: true)).to be_flag + subject.class_eval do + attribute :flag, type: :boolean + end + + ['false', false, '0', 0].each do |falsey| + expect(subject.new(flag: falsey).flag).to be_falsey + end + + ['true', true, '1', 1].each do |truthy| + expect(subject.new(flag: truthy).flag).to be_truthy + end end it 'should parse an array' do - expect(TypeSpec.new(list: []).list).to eq([]) - expect(TypeSpec.new(list: 'item').list).to eq(['item']) + subject.class_eval do + attribute :list, type: :array + end + + expect(subject.new(list: []).list).to eq([]) + expect(subject.new(list: 'item').list).to eq(['item']) end it 'should parse a float' do - expect(TypeSpec.new(floater: '0.01').floater).to eq(0.01) - expect(TypeSpec.new(floater: 0.01).floater).to eq(0.01) + subject.class_eval do + attribute :floater, type: :float + end + + expect(subject.new(floater: '0.01').floater).to eq(0.01) + expect(subject.new(floater: 0.01).floater).to eq(0.01) end it 'should use custom parser' do - expect(TypeSpec.new(custom: '15').custom).to eq('X!15') + subject.class_eval do + attribute :custom, parser: lambda { |v, _| "X!#{v}" } + end + + expect(subject.new(custom: '15').custom).to eq('X!15') end - it 'should squash, cast, alias an attribute and keep a vanilla reference' do + it 'squashes, casts and aliases an attribute and keeps a vanilla reference' do + subject.class_eval do + attribute :butternut_id, squash: %w(squash id), type: :integer + attribute :butternut_type, squash: %w(squash type) + attribute :squash + attribute :vegetable, aliases: 'squash' + end + # vanilla squash - expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_type).to eq('fred') - expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => nil } }).butternut_type).to be_nil - expect(TypeSpec.new({ 'squash' => nil }).butternut_type).to be_nil + expect(subject.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_type).to eq('fred') + expect(subject.new({ 'squash' => { 'id' => '12', 'type' => nil } }).butternut_type).to be_nil + expect(subject.new({ 'squash' => nil }).butternut_type).to be_nil # composite processors: squash and cast - expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_id).to eq(12) - expect(TypeSpec.new({ 'squash' => { 'id' => nil, 'type' => 'fred' } }).butternut_id).to be_nil - expect(TypeSpec.new({ 'squash' => { 'type' => 'fred' } }).butternut_id).to be_nil + expect(subject.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).butternut_id).to eq(12) + expect(subject.new({ 'squash' => { 'id' => nil, 'type' => 'fred' } }).butternut_id).to be_nil + expect(subject.new({ 'squash' => { 'type' => 'fred' } }).butternut_id).to be_nil # override intermediate processing - expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).squash).to eq({ 'id' => '12', 'type' => 'fred' }) + expect(subject.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).squash).to eq({ 'id' => '12', 'type' => 'fred' }) # alias of override - expect(TypeSpec.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).vegetable).to eq({ 'id' => '12', 'type' => 'fred' }) + expect(subject.new({ 'squash' => { 'id' => '12', 'type' => 'fred' } }).vegetable).to eq({ 'id' => '12', 'type' => 'fred' }) end - it 'should set a default value' do - expect(TypeSpec.new.default).to eq('im a squash') + it 'sets a default value' do + subject.class_eval do + attribute :default, default: 'im a squash' + end + + expect(subject.new.default).to eq('im a squash') end it 'should override a default value' do - expect(TypeSpec.new(default: 'now im a different squash').default).to eq('now im a different squash') + subject.class_eval do + attribute :default, default: 'im a squash' + end + + expect(subject.new(default: 'now im a different squash').default).to eq('now im a different squash') end context 'allowing the same alias for multiple attributes' do + before { + subject.class_eval do + attribute :same_alias_1, aliases: 'nested' + attribute :same_alias_2, aliases: 'nested' + + attribute :same_alias_squashed_1, squash: %w(nested attr_1) + attribute :same_alias_squashed_2, squash: %w(nested attr_2) + attribute :same_alias_squashed_3, squash: %w(nested attr_2) + end + } + it 'should do so when not squashing' do - type_spec = TypeSpec.new({ 'nested' => 'bamboo' }) - expect(type_spec.same_alias_1).to eq('bamboo') - expect(type_spec.same_alias_2).to eq('bamboo') + model = subject.new('nested' => 'bamboo') + + expect(model.same_alias_1).to eq('bamboo') + expect(model.same_alias_2).to eq('bamboo') end it 'should do so when squashing' do - type_spec = TypeSpec.new({ 'nested' => { 'attr_1' => 'bamboo', 'attr_2' => 'panda' } }) - expect(type_spec.same_alias_squashed_1).to eq('bamboo') - expect(type_spec.same_alias_squashed_2).to eq('panda') - expect(type_spec.same_alias_squashed_3).to eq('panda') + model = subject.new('nested' => { 'attr_1' => 'bamboo', 'attr_2' => 'panda' }) + + expect(model.same_alias_squashed_1).to eq('bamboo') + expect(model.same_alias_squashed_2).to eq('panda') + expect(model.same_alias_squashed_3).to eq('panda') end end it 'should slice out unaccounted for attributes' do - expect(TypeSpec.new({ 'something' => { 'id' => '12' } }).attributes.keys).not_to include('something') + expect(subject.new({ 'something' => { 'id' => '12' } }).attributes.keys).not_to include('something') end end