HornsAndHooves-flat_map 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.metrics +17 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +20 -0
- data/HornsAndHooves-flat_map.gemspec +29 -0
- data/LICENSE +21 -0
- data/README.markdown +214 -0
- data/Rakefile +15 -0
- data/lib/flat_map.rb +14 -0
- data/lib/flat_map/errors.rb +57 -0
- data/lib/flat_map/mapping.rb +124 -0
- data/lib/flat_map/mapping/factory.rb +21 -0
- data/lib/flat_map/mapping/reader.rb +12 -0
- data/lib/flat_map/mapping/reader/basic.rb +28 -0
- data/lib/flat_map/mapping/reader/formatted.rb +45 -0
- data/lib/flat_map/mapping/reader/formatted/formats.rb +28 -0
- data/lib/flat_map/mapping/reader/method.rb +25 -0
- data/lib/flat_map/mapping/reader/proc.rb +15 -0
- data/lib/flat_map/mapping/writer.rb +11 -0
- data/lib/flat_map/mapping/writer/basic.rb +25 -0
- data/lib/flat_map/mapping/writer/method.rb +28 -0
- data/lib/flat_map/mapping/writer/proc.rb +18 -0
- data/lib/flat_map/model_mapper.rb +195 -0
- data/lib/flat_map/model_mapper/persistence.rb +108 -0
- data/lib/flat_map/model_mapper/skipping.rb +45 -0
- data/lib/flat_map/open_mapper.rb +113 -0
- data/lib/flat_map/open_mapper/attribute_methods.rb +55 -0
- data/lib/flat_map/open_mapper/factory.rb +244 -0
- data/lib/flat_map/open_mapper/mapping.rb +123 -0
- data/lib/flat_map/open_mapper/mounting.rb +168 -0
- data/lib/flat_map/open_mapper/persistence.rb +178 -0
- data/lib/flat_map/open_mapper/skipping.rb +66 -0
- data/lib/flat_map/open_mapper/traits.rb +95 -0
- data/lib/flat_map/version.rb +3 -0
- data/spec/flat_map/errors_spec.rb +23 -0
- data/spec/flat_map/mapper/attribute_methods_spec.rb +36 -0
- data/spec/flat_map/mapper/callbacks_spec.rb +76 -0
- data/spec/flat_map/mapper/factory_spec.rb +285 -0
- data/spec/flat_map/mapper/mapping_spec.rb +98 -0
- data/spec/flat_map/mapper/mounting_spec.rb +142 -0
- data/spec/flat_map/mapper/persistence_spec.rb +152 -0
- data/spec/flat_map/mapper/skipping_spec.rb +91 -0
- data/spec/flat_map/mapper/targeting_spec.rb +156 -0
- data/spec/flat_map/mapper/traits_spec.rb +172 -0
- data/spec/flat_map/mapper/validations_spec.rb +72 -0
- data/spec/flat_map/mapper_spec.rb +9 -0
- data/spec/flat_map/mapping/factory_spec.rb +12 -0
- data/spec/flat_map/mapping/reader/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/reader/formatted_spec.rb +62 -0
- data/spec/flat_map/mapping/reader/method_spec.rb +13 -0
- data/spec/flat_map/mapping/reader/proc_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/basic_spec.rb +15 -0
- data/spec/flat_map/mapping/writer/method_spec.rb +13 -0
- data/spec/flat_map/mapping/writer/proc_spec.rb +13 -0
- data/spec/flat_map/mapping_spec.rb +123 -0
- data/spec/flat_map/open_mapper_spec.rb +19 -0
- data/spec/spec_helper.rb +7 -0
- data/tmp/metric_fu/_data/20131218.yml +6902 -0
- data/tmp/metric_fu/_data/20131219.yml +6726 -0
- metadata +220 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This helper module provides helper functionality that allow to
|
3
|
+
# exclude specific mapper from a processing chain.
|
4
|
+
module OpenMapper::Skipping
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :ActiveRecord
|
8
|
+
|
9
|
+
# Mark self as skipped, i.e. it will not be subject of
|
10
|
+
# validation and saving chain.
|
11
|
+
#
|
12
|
+
# @return [Object]
|
13
|
+
def skip!
|
14
|
+
@_skip_processing = true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Remove "skip" mark from +self+ and "destroyed" flag from
|
18
|
+
# the target.
|
19
|
+
#
|
20
|
+
# @return [Object]
|
21
|
+
def use!
|
22
|
+
@_skip_processing = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Return +true+ if +self+ was marked for skipping.
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def skipped?
|
29
|
+
!!@_skip_processing
|
30
|
+
end
|
31
|
+
|
32
|
+
# Override {FlatMap::OpenMapper::Persistence#valid?} to
|
33
|
+
# force it to return +true+ if +self+ is marked for skipping.
|
34
|
+
#
|
35
|
+
# @param [Symbol] context useless context parameter to make it compatible with
|
36
|
+
# ActiveRecord models.
|
37
|
+
#
|
38
|
+
# @return [Boolean]
|
39
|
+
def valid?(context = nil)
|
40
|
+
skipped? || super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override {FlatMap::OpenMapper::Persistence#save} method to
|
44
|
+
# force it to return +true+ if +self+ is marked for skipping.
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
def save
|
48
|
+
skipped? || super
|
49
|
+
end
|
50
|
+
|
51
|
+
# Override {FlatMap::OpenMapper::Persistence#shallow_save} method
|
52
|
+
# to make it possible to skip traits.
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def shallow_save
|
56
|
+
skipped? || super
|
57
|
+
end
|
58
|
+
|
59
|
+
# Mark self as used and then delegated to original
|
60
|
+
# {FlatMap::OpenMapper::Persistence#write}.
|
61
|
+
def write(*)
|
62
|
+
use!
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module FlatMap
|
2
|
+
# This small module allows mappers to define traits, which technically
|
3
|
+
# means mounting anonymous mappers, attached to host one.
|
4
|
+
#
|
5
|
+
# Also, FlatMap::OpenMapper::Mounting#mountings completely overridden
|
6
|
+
# here to support special trait behavior.
|
7
|
+
module OpenMapper::Traits
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
# Traits class macros
|
11
|
+
module ClassMethods
|
12
|
+
# Define a trait for a mapper class. In implementation terms, a trait
|
13
|
+
# is nothing more than a mounted mapper, owned by a host mapper.
|
14
|
+
# It shares all mappings with it.
|
15
|
+
# The block is passed as a body of the anonymous mapper class.
|
16
|
+
#
|
17
|
+
# @param [Symbol] name
|
18
|
+
def trait(name, &block)
|
19
|
+
base_class = self < FlatMap::Mapper ? FlatMap::Mapper : FlatMap::OpenMapper
|
20
|
+
mapper_class = Class.new(base_class, &block)
|
21
|
+
mapper_class_name = "#{ancestors.first.name}#{name.to_s.camelize}Trait"
|
22
|
+
mapper_class.singleton_class.send(:define_method, :name){ mapper_class_name }
|
23
|
+
mount mapper_class, :trait_name => name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Override the original {FlatMap::OpenMapper::Mounting#mountings}
|
28
|
+
# method to filter out those traited mappers that are not required for
|
29
|
+
# trait setup of +self+. Also, handle any inline extension that may be
|
30
|
+
# defined on the mounting mapper, which is attached as a singleton trait.
|
31
|
+
#
|
32
|
+
# @return [Array<FlatMap::OpenMapper>]
|
33
|
+
def mountings
|
34
|
+
@mountings ||= begin
|
35
|
+
mountings = self.class.mountings.reject do |factory|
|
36
|
+
factory.traited? && !factory.required_for_any_trait?(traits)
|
37
|
+
end
|
38
|
+
mountings.concat(singleton_class.mountings)
|
39
|
+
mountings.map{ |factory| factory.create(self, *traits) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a list of all mountings that represent full picture of +self+, i.e.
|
44
|
+
# +self+ and all traits, including deeply nested, that are mounted on self
|
45
|
+
#
|
46
|
+
# @return [Array<FlatMap::OpenMapper>]
|
47
|
+
def self_mountings
|
48
|
+
mountings.select(&:owned?).map{ |mount| mount.self_mountings }.flatten.concat [self]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Try to find trait mapper with name that corresponds to +trait_name+
|
52
|
+
# Used internally to manipulate such mappers (for example, skip some traits)
|
53
|
+
# in some scenarios.
|
54
|
+
#
|
55
|
+
# @param [Symbol] trait_name
|
56
|
+
# @return [FlatMap::OpenMapper, nil]
|
57
|
+
def trait(trait_name)
|
58
|
+
self_mountings.find{ |mount| mount.class.name.underscore =~ /#{trait_name}_trait$/ }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return :extension trait, if present
|
62
|
+
#
|
63
|
+
# @return [FlatMap::OpenMapper]
|
64
|
+
def extension
|
65
|
+
trait(:extension)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return only mountings that are actually traits for host mapper.
|
69
|
+
#
|
70
|
+
# @return [Array<FlatMap::OpenMapper>]
|
71
|
+
def trait_mountings
|
72
|
+
result = mountings.select{ |mount| mount.owned? }
|
73
|
+
# mapper extension has more priority then traits, and
|
74
|
+
# has to be processed first.
|
75
|
+
result.unshift(result.pop) if result.length > 1 && result[-1].extension?
|
76
|
+
result
|
77
|
+
end
|
78
|
+
protected :trait_mountings
|
79
|
+
|
80
|
+
# Return only mountings that correspond to external mappers.
|
81
|
+
#
|
82
|
+
# @return [Array<FlatMap::OpenMapper>]
|
83
|
+
def mapper_mountings
|
84
|
+
mountings.select{ |mount| !mount.owned? }
|
85
|
+
end
|
86
|
+
protected :mapper_mountings
|
87
|
+
|
88
|
+
# Return +true+ if +self+ is extension of host mapper.
|
89
|
+
#
|
90
|
+
# @return [Boolean]
|
91
|
+
def extension?
|
92
|
+
owned? && self.class.name =~ /ExtensionTrait$/
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FlatMap::Errors do
|
4
|
+
let(:errors) {
|
5
|
+
described_class.new(
|
6
|
+
double( 'mapper', :suffixed? => true,
|
7
|
+
:suffix => 'foo',
|
8
|
+
'attr_foo' => 2)
|
9
|
+
)
|
10
|
+
}
|
11
|
+
|
12
|
+
it "preserved errors should appear on #empty? call exactly once" do
|
13
|
+
errors.preserve :base, 'an error'
|
14
|
+
errors.should_not be_empty
|
15
|
+
errors[:base].should == ['an error']
|
16
|
+
expect{ errors.empty? }.not_to change{ errors[:base].length }
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should add error to mapper with suffix" do
|
20
|
+
errors.add(:attr, 'an error')
|
21
|
+
errors[:attr_foo].should == ['an error']
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FlatMap
|
4
|
+
module AttributeMethodsSpec
|
5
|
+
class SpecMapper < Mapper
|
6
|
+
map :attr_a, :attr_b
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'Attribute Methods' do
|
11
|
+
let(:target){ OpenStruct.new }
|
12
|
+
let(:mapper){ AttributeMethodsSpec::SpecMapper.new(target) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
target.attr_a = 'a'
|
16
|
+
target.attr_b = 'b'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should be able to read values via method calls' do
|
20
|
+
mapper.attr_a.should == 'a'
|
21
|
+
mapper.attr_b.should == 'b'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should be able to write values via method calls' do
|
25
|
+
mapper.attr_a = 'A'
|
26
|
+
mapper.attr_b = 'B'
|
27
|
+
target.attr_a.should == 'A'
|
28
|
+
target.attr_b.should == 'B'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should still raise for unknown or private method calls' do
|
32
|
+
expect{ mapper.undefined_method }.to raise_error(NoMethodError)
|
33
|
+
expect{ mapper.attribute_methods }.to raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FlatMap
|
4
|
+
module CallbacksSpec
|
5
|
+
class MountMapper < Mapper
|
6
|
+
map :attr_c
|
7
|
+
|
8
|
+
set_callback :validate, :before, :set_c
|
9
|
+
|
10
|
+
def set_c
|
11
|
+
self.attr_c = 'mounted before validate'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class SpecMapper < Mapper
|
16
|
+
map :attr_a
|
17
|
+
|
18
|
+
set_callback :save, :before, :set_a
|
19
|
+
|
20
|
+
trait :with_b do
|
21
|
+
map :attr_b
|
22
|
+
|
23
|
+
set_callback :validate, :before, :set_b
|
24
|
+
|
25
|
+
def set_b
|
26
|
+
self.attr_b = 'before validate'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_a
|
31
|
+
self.attr_a = 'before save'
|
32
|
+
end
|
33
|
+
|
34
|
+
mount :mount,
|
35
|
+
:mapper_class_name => 'FlatMap::CallbacksSpec::MountMapper',
|
36
|
+
:target => lambda{ |_| OpenStruct.new }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'Callbacks' do
|
41
|
+
let(:mapper) do
|
42
|
+
CallbacksSpec::SpecMapper.new(OpenStruct.new, :with_b) do
|
43
|
+
set_callback :validate, :before, :extension_set_b
|
44
|
+
|
45
|
+
def extension_set_b
|
46
|
+
self.attr_b = 'extension value'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should call callbacks once' do
|
52
|
+
mapper.should_receive(:set_a).once
|
53
|
+
mapper.save
|
54
|
+
end
|
55
|
+
|
56
|
+
specify 'validation callbacks' do
|
57
|
+
mapper.valid?
|
58
|
+
mapper.attr_a.should be_nil
|
59
|
+
mapper.attr_b.should == 'before validate'
|
60
|
+
mapper.attr_c.should == 'mounted before validate'
|
61
|
+
end
|
62
|
+
|
63
|
+
specify 'save callbacks' do
|
64
|
+
mapper.save
|
65
|
+
mapper.attr_a.should == 'before save'
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'extension trait and named traits' do
|
69
|
+
it 'should process extension first' do
|
70
|
+
mapper.extension.should_receive(:extension_set_b).once.and_call_original
|
71
|
+
mapper.valid?
|
72
|
+
mapper.attr_b.should == 'before validate'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FlatMap
|
4
|
+
describe OpenMapper::Factory do
|
5
|
+
let(:trait_class){ Class.new(FlatMap::OpenMapper) }
|
6
|
+
let(:mapper){ OpenMapper.build }
|
7
|
+
|
8
|
+
let(:mount_factory){ OpenMapper::Factory.new(:spec_mount, :traits => :used_traits) }
|
9
|
+
let(:trait_factory){ OpenMapper::Factory.new(trait_class, :trait_name => :a_trait) }
|
10
|
+
let(:open_factory){ OpenMapper::Factory.new(:some_mount, :open => true) }
|
11
|
+
|
12
|
+
context 'when used for a trait' do
|
13
|
+
subject{ trait_factory }
|
14
|
+
|
15
|
+
it{ should be_traited }
|
16
|
+
its(:name){ should be_nil }
|
17
|
+
its(:trait_name){ should == :a_trait }
|
18
|
+
its(:mapper_class){ should == trait_class }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when used for a mounted mapper' do
|
22
|
+
subject{ mount_factory }
|
23
|
+
|
24
|
+
it{ should_not be_traited }
|
25
|
+
its(:name){ should == :spec_mount }
|
26
|
+
its(:trait_name){ should be_nil }
|
27
|
+
its(:traits){ should == [:used_traits] }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when used for an open mapper' do
|
31
|
+
it "should have descendant of OpenMapper as mapper_class" do
|
32
|
+
open_factory.mapper_class.should < OpenMapper
|
33
|
+
open_factory.mapper_class.name.should == 'SomeMountMapper'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should create and instance of OpenMapper with open struct as a target" do
|
37
|
+
mounted = open_factory.create(mapper)
|
38
|
+
mounted.target.should be_a(OpenStruct)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'behavior' do
|
43
|
+
let(:target) { mapper.target }
|
44
|
+
let(:other_target) { Object.new }
|
45
|
+
|
46
|
+
describe '#mapper_class for mounted mappers' do
|
47
|
+
class ::SpecMountMapper < FlatMap::ModelMapper; end
|
48
|
+
class OpenMapper::Factory::SpecMountMapper < FlatMap::OpenMapper; end
|
49
|
+
|
50
|
+
it "should be able to fetch class name from name" do
|
51
|
+
mount_factory.mapper_class.should == ::SpecMountMapper
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be able to fetch class from mapper_class" do
|
55
|
+
factory = OpenMapper::Factory.new(
|
56
|
+
:spec_mount,
|
57
|
+
:mapper_class => FlatMap::OpenMapper::Factory::SpecMountMapper
|
58
|
+
)
|
59
|
+
factory.mapper_class.should == ::FlatMap::OpenMapper::Factory::SpecMountMapper
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should use options if specified" do
|
63
|
+
factory = OpenMapper::Factory.new(
|
64
|
+
:spec_mount,
|
65
|
+
:mapper_class_name => 'FlatMap::OpenMapper::Factory::SpecMountMapper'
|
66
|
+
)
|
67
|
+
factory.mapper_class.should == ::FlatMap::OpenMapper::Factory::SpecMountMapper
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#fetch_target_from' do
|
72
|
+
it "should return owner's target for traited factory" do
|
73
|
+
trait_factory.fetch_target_from(mapper).should == target
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'explicit target' do
|
77
|
+
it "should use explicitly specified if applicable" do
|
78
|
+
factory = OpenMapper::Factory.new(:spec_mount, :target => other_target)
|
79
|
+
factory.fetch_target_from(mapper).should == other_target
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should call Proc and pass owner target to it if Proc is specified as :target" do
|
83
|
+
factory = OpenMapper::Factory.new(:spec_mount, :target => lambda{ |obj| obj.foo })
|
84
|
+
target.should_receive(:foo).and_return(other_target)
|
85
|
+
factory.fetch_target_from(mapper).should == other_target
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should call a method if Symbol is used" do
|
89
|
+
factory = OpenMapper::Factory.new(:spec_mount, :target => :foo)
|
90
|
+
mapper.should_receive(:foo).and_return(other_target)
|
91
|
+
factory.fetch_target_from(mapper).should == other_target
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'target from association' do
|
96
|
+
before do
|
97
|
+
target.stub(:is_a?).and_call_original
|
98
|
+
target.stub(:is_a?).with(ActiveRecord::Base).and_return(true)
|
99
|
+
end
|
100
|
+
|
101
|
+
let(:has_one_current_reflection) {
|
102
|
+
double('reflection', :macro => :has_one, :options => {:is_current => true})
|
103
|
+
}
|
104
|
+
let(:has_one_reflection) {
|
105
|
+
double('reflection', :macro => :has_one, :options => {})
|
106
|
+
}
|
107
|
+
let(:belongs_to_reflection) {
|
108
|
+
double('reflection', :macro => :belongs_to)
|
109
|
+
}
|
110
|
+
let(:has_many_reflection) {
|
111
|
+
double('reflection', :macro => :has_many, :name => :spec_mounts)
|
112
|
+
}
|
113
|
+
|
114
|
+
it "should refer to effective name for has_one_current association" do
|
115
|
+
# Note: has_one_current is not part of Rails
|
116
|
+
mount_factory.should_receive(:reflection_from_target).
|
117
|
+
with(target).
|
118
|
+
and_return(has_one_current_reflection)
|
119
|
+
target.should_receive(:effective_spec_mount).and_return(other_target)
|
120
|
+
mount_factory.fetch_target_from(mapper).should == other_target
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should refer to existing association object if possible, " \
|
124
|
+
"and build it if it is absent for :has_one" do
|
125
|
+
mount_factory.should_receive(:reflection_from_target).
|
126
|
+
with(target).
|
127
|
+
and_return(has_one_reflection)
|
128
|
+
target.should_receive(:spec_mount).and_return(nil)
|
129
|
+
target.should_receive(:build_spec_mount).and_return(other_target)
|
130
|
+
mount_factory.fetch_target_from(mapper).should == other_target
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should refer to existing association object if possible, " \
|
134
|
+
"and build it if it is absent for :belongs_to" do
|
135
|
+
mount_factory.should_receive(:reflection_from_target).
|
136
|
+
with(target).
|
137
|
+
and_return(belongs_to_reflection)
|
138
|
+
target.should_receive(:spec_mount).and_return(nil)
|
139
|
+
target.should_receive(:build_spec_mount).and_return(other_target)
|
140
|
+
mount_factory.fetch_target_from(mapper).should == other_target
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should always build a new record for :has_many association" do
|
144
|
+
mount_factory.should_receive(:reflection_from_target).
|
145
|
+
with(target).
|
146
|
+
and_return(has_many_reflection)
|
147
|
+
target.should_receive(:association).with(:spec_mounts)
|
148
|
+
target.stub_chain(:association, :build).and_return(other_target)
|
149
|
+
mount_factory.fetch_target_from(mapper).should == other_target
|
150
|
+
end
|
151
|
+
|
152
|
+
describe 'reflection_from_target' do
|
153
|
+
before{ target.stub(:is_a?).with(ActiveRecord::Base).and_return(true) }
|
154
|
+
|
155
|
+
it 'should first refer to singular association' do
|
156
|
+
target.stub_chain(:class, :reflect_on_association).
|
157
|
+
with(:spec_mount).
|
158
|
+
and_return(has_one_reflection)
|
159
|
+
mount_factory.reflection_from_target(target).should == has_one_reflection
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should use collection association if singular does not exist' do
|
163
|
+
target.stub_chain(:class, :reflect_on_association).
|
164
|
+
with(:spec_mount).
|
165
|
+
and_return(nil)
|
166
|
+
target.stub_chain(:class, :reflect_on_association).
|
167
|
+
with(:spec_mounts).
|
168
|
+
and_return(has_many_reflection)
|
169
|
+
mount_factory.reflection_from_target(target).should == has_many_reflection
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'target from name' do
|
175
|
+
it 'should simply send method to owner target' do
|
176
|
+
target.should_receive(:spec_mount).and_return(other_target)
|
177
|
+
mount_factory.fetch_target_from(mapper).should == other_target
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#create' do
|
183
|
+
specify 'traited factory should create an owned mapper' do
|
184
|
+
new_one = trait_factory.create(mapper)
|
185
|
+
new_one.owner.should == mapper
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'mounted mapper' do
|
189
|
+
let(:mount_class){ Class.new(Mapper) }
|
190
|
+
let(:factory){ mount_factory }
|
191
|
+
|
192
|
+
before do
|
193
|
+
factory.stub(:mapper_class).and_return(mount_class)
|
194
|
+
factory.stub(:fetch_target_from).and_return(other_target)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should combine traits' do
|
198
|
+
mount_class.should_receive(:new).
|
199
|
+
with(other_target, :used_traits, :another_trait).
|
200
|
+
and_call_original
|
201
|
+
factory.create(mapper, :another_trait)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should properly set properties' do
|
205
|
+
new_one = factory.create(mapper)
|
206
|
+
new_one.host .should == mapper
|
207
|
+
new_one.name .should == :spec_mount
|
208
|
+
new_one.save_order.should == :after
|
209
|
+
new_one.suffix.should be_nil
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'when suffix is defined' do
|
213
|
+
let(:factory){ OpenMapper::Factory.new(:spec_mount, :suffix => :foo) }
|
214
|
+
|
215
|
+
it "should adjust properties with suffix" do
|
216
|
+
new_one = factory.create(mapper)
|
217
|
+
new_one.name .should == :spec_mount_foo
|
218
|
+
new_one.suffix.should == :foo
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context 'when extension is present' do
|
223
|
+
let(:extension){ Proc.new{} }
|
224
|
+
let(:factory){ OpenMapper::Factory.new(:spec_mount, &extension) }
|
225
|
+
|
226
|
+
it "should pass it to mapper initialization" do
|
227
|
+
mount_class.should_receive(:new).
|
228
|
+
with(other_target, &extension).
|
229
|
+
and_call_original
|
230
|
+
new_one = factory.create(mapper)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe 'save order' do
|
235
|
+
before do
|
236
|
+
mapper.stub(:is_a?).and_call_original
|
237
|
+
mapper.stub(:is_a?).with(ModelMapper).and_return(true)
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should be :before for belongs_to association' do
|
241
|
+
factory.stub(:reflection_from_target).
|
242
|
+
and_return(double('reflection', :macro => :belongs_to))
|
243
|
+
factory.fetch_save_order(mapper).should == :before
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should be :after for other cases' do
|
247
|
+
factory.stub(:reflection_from_target).
|
248
|
+
and_return(double('reflection', :macro => :has_one))
|
249
|
+
factory.fetch_save_order(mapper).should == :after
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'when explicitly set' do
|
253
|
+
let(:factory){ OpenMapper::Factory.new(:spec_mount, :save => :before) }
|
254
|
+
|
255
|
+
it 'should fetch from options, if possible' do
|
256
|
+
new_one = factory.create(mapper)
|
257
|
+
new_one.save_order.should == :before
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe '#required_for_any_trait?' do
|
265
|
+
let(:mapper_class) do
|
266
|
+
Class.new(FlatMap::Mapper) do
|
267
|
+
trait(:trait_a) {
|
268
|
+
trait(:trait_b) {
|
269
|
+
trait(:trait_c) {
|
270
|
+
} } }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
let(:factory_for_b){ mapper_class.mountings.first.mapper_class.mountings.first }
|
274
|
+
|
275
|
+
it "should be required for nested trait" do
|
276
|
+
factory_for_b.required_for_any_trait?([:trait_c]).should be true
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should not be required for top trait" do
|
280
|
+
factory_for_b.required_for_any_trait?([:trait_a]).should be false
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|