require 'spec_helper' require 'json' describe Her::LazyRelations do let(:klass) do |*args| ctx = self Class.new do include Her::Model include Her::LazyRelations lazy :relation, ctx.related_class lazy :chained, self lazy_collection :relations, ctx.related_class lazy_collection :parents, self def request_path "/parents/#{id}" end end end let(:related_class) do Class.new do include Her::Model def request_path "/things/#{id}" end def standalone_method "a/b/c" end def satisified_dependent_method "-#{local}-" end def unsatisfied_dependent_method "-#{remote}-" end def shadow "-#{super}-" end def remote_shadow "-#{super}-" end end end let(:local_data) { { :id => 1, :local => 'data', :shadow => 'x' } } let(:remote_data) { local_data.merge(:remote => 'DATA', :remote_shadow => 'X') } describe '.lazy' do subject { klass.new(:relation => local_data).relation } it { should be_a(related_class) } it 'does not call methods to #inspect' do expect(subject).not_to receive(:shadow) subject.inspect end describe 'local attributes' do before { expect(related_class).not_to receive(:request) } example 'allow access to local attributes' do expect(subject.local).to eql('data') end example 'provide local attributes predicates' do expect(subject.local?).to be true end example 'provide local attributes setters' do subject.local = 'foo' expect(subject.local).to eql('foo') end example 'allow access to local standalone methods' do expect(subject.standalone_method).to eql('a/b/c') end example 'allow access to locally satisfiable methods' do expect(subject.satisified_dependent_method).to eql('-data-') end example 'allow `super` access to shadowed attributes' do expect(subject.shadow).to eql('-x-') end end describe 'remote attributes' do before do stub_api_for(related_class) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end example 'allow access to remote attributes' do expect(subject.remote).to eql('DATA') end example 'provide remote attributes predicates' do expect(subject.remote?).to be true end example 'provide remote attributes setters' do subject.remote = 'foo' expect(subject.remote).to eql('foo') end example 'allow multiple instances to access remote attributes' do expect(related_class).to receive(:request) \ .exactly(9).times \ .and_call_original 9.times do subject = klass.new(:relation => local_data).relation expect(subject.remote).to eql('DATA') end end example 'allow access to locally unsatisfiable methods' do expect(subject.unsatisfied_dependent_method).to eql('-DATA-') end example 'allow `super` access to shadowed remote attributes' do expect(subject.remote_shadow).to eql('-X-') end end describe 'remote relations' do before do stub_api_for(klass) do |api| api.get('/parents/1') do data = { :id => 1, :relation => local_data } [ 200, { 'Content-Type' => 'json' }, data.to_json ] end end stub_api_for(related_class) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end subject { klass.new(:chained => { :id => 1 }) } example 'allow chained lookups of lazy relations' do expect(subject.chained.relation.remote).to eql('DATA') end end describe 'null relations' do subject { klass.new(:relation => nil) } example 'do not return new instances' do expect(subject.relation).to be nil end end describe 'unsatisfiable attributes' do before do stub_api_for(klass) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end example 'raise an exception when accessing an unknown attribute' do expect { subject.unknown_attribute }.to raise_error(NoMethodError) end end end describe '.lazy_collection' do subject { klass.new(:relations => [local_data]).relations.first } it { should be_a(related_class) } it 'does not call methods to #inspect' do expect(subject).not_to receive(:shadow) subject.inspect end describe 'local attributes' do before { expect(related_class).not_to receive(:request) } example 'allow access to local attributes' do expect(subject.local).to eql('data') end example 'provide local attributes predicates' do expect(subject.local?).to be true end example 'provide local attributes setters' do subject.local = 'foo' expect(subject.local).to eql('foo') end example 'allow access to local standalone methods' do expect(subject.standalone_method).to eql('a/b/c') end example 'allow access to locally satisfiable methods' do expect(subject.satisified_dependent_method).to eql('-data-') end example 'allow `super` access to shadowed attributes' do expect(subject.shadow).to eql('-x-') end end describe 'remote attributes' do before do stub_api_for(related_class) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end example 'allow access to remote attributes' do expect(subject.remote).to eql('DATA') end example 'provide remote attributes predicates' do expect(subject.remote?).to be true end example 'provide remote attributes setters' do subject.remote = 'foo' expect(subject.remote).to eql('foo') end example 'allow multiple instances to access remote attributes' do expect(related_class).to receive(:request) \ .exactly(9).times \ .and_call_original 9.times do subject = klass.new(:relations => [local_data]).relations.first expect(subject.remote).to eql('DATA') end end example 'allow access to locally unsatisfiable methods' do expect(subject.unsatisfied_dependent_method).to eql('-DATA-') end example 'allow `super` access to shadowed remote attributes' do expect(subject.remote_shadow).to eql('-X-') end end describe 'remote relations' do before do stub_api_for(klass) do |api| api.get('/parents/1') do data = { :id => 1, :parents => [{ :id => 1, :relation => local_data }] } [ 200, { 'Content-Type' => 'json' }, data.to_json ] end end stub_api_for(related_class) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end subject { klass.new(:id => 1) } example 'allow chained lookups of lazy relations' do expect(subject.parents[0].relation.remote).to eql('DATA') end end describe 'null relations' do subject { klass.new(:relations => nil) } example 'return an empty list' do expect(subject.relations).to be_empty end end describe 'unsatisfiable attributes' do before do stub_api_for(klass) do |api| api.get('/things/1') do [ 200, { 'Content-Type' => 'json' }, remote_data.to_json ] end end end example 'raise an exception when accessing an unknown attribute' do expect { subject.unknown_attribute }.to raise_error(NoMethodError) end end end end