datamappify 0.50.0 → 0.51.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +78 -4
  4. data/lib/datamappify/data/criteria/active_record/find_multiple.rb +28 -0
  5. data/lib/datamappify/data/criteria/common.rb +28 -8
  6. data/lib/datamappify/data/criteria/relational/find_multiple.rb +62 -3
  7. data/lib/datamappify/data/criteria/sequel/find_multiple.rb +31 -0
  8. data/lib/datamappify/data/errors.rb +3 -0
  9. data/lib/datamappify/data/mapper/attribute.rb +81 -3
  10. data/lib/datamappify/data/mapper.rb +12 -12
  11. data/lib/datamappify/data/provider/active_record.rb +20 -5
  12. data/lib/datamappify/data/provider/common_provider.rb +2 -0
  13. data/lib/datamappify/data/provider/sequel.rb +25 -6
  14. data/lib/datamappify/data/record.rb +18 -5
  15. data/lib/datamappify/entity/composable.rb +63 -7
  16. data/lib/datamappify/entity/relation.rb +1 -1
  17. data/lib/datamappify/repository/inheritable.rb +28 -0
  18. data/lib/datamappify/repository/query_method/find_multiple.rb +1 -7
  19. data/lib/datamappify/repository/query_method/method.rb +2 -2
  20. data/lib/datamappify/repository/query_methods.rb +8 -4
  21. data/lib/datamappify/repository.rb +2 -0
  22. data/lib/datamappify/version.rb +1 -1
  23. data/spec/entity/composable_spec.rb +34 -11
  24. data/spec/entity/inheritance_spec.rb +33 -0
  25. data/spec/entity/relation_spec.rb +11 -9
  26. data/spec/repository/finders_spec.rb +185 -7
  27. data/spec/support/entities/admin_user.rb +5 -0
  28. data/spec/support/entities/computer.rb +2 -0
  29. data/spec/support/entities/computer_hardware.rb +4 -0
  30. data/spec/support/entities/computer_software.rb +3 -0
  31. data/spec/support/repositories/active_record/admin_user_repository.rb +5 -0
  32. data/spec/support/repositories/sequel/admin_user_repository.rb +5 -0
  33. data/spec/support/tables/active_record.rb +1 -0
  34. data/spec/support/tables/sequel.rb +1 -0
  35. data/spec/unit/entity/relation_spec.rb +12 -2
  36. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51bd14425d7e0347cf2a97adff354cb262038152
4
- data.tar.gz: f7fc7a27a71d6e5cb10c971f9a7e57fc968ac8d0
3
+ metadata.gz: f9765005a9b047c0dabab75eeb3da7997761c57d
4
+ data.tar.gz: c6747c591e913b7565a730ea7192703991aaf2b6
5
5
  SHA512:
6
- metadata.gz: ef748d505249785e6da9518a05eaab78cca206517eb708900e43e4342bedf01be6f2c369f1570058866dc10a7ac4c7a560b5ddb0371c4b35e0a6b3abdc2e2eaf
7
- data.tar.gz: e7a1d50c78433c4945b06507ad60fe366f7f9623fa806cafbf54a3b99229232dafca583ea31e1d3a827729eeffcf108f4712e607b5f80fde005abf66782885e4
6
+ metadata.gz: 64566dfe4f603db5104657ca9416aef1d96f4b0327de25c0c9a36a68285b3dcf049efd2c39381f5f4f2ab7b781509c13f0c4ef27b02066e9b634e5cd23c56720
7
+ data.tar.gz: 0bc81a94c2c52c1b3da152d79f39405be5050e2242c526d628650591a4e741250f5d47136c93ef96a2a1f63f6c264200754ad603ea41954aafaffb2ce2f91cda
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## master
2
2
 
3
+ ## 0.51.0 [2013-06-17]
4
+
5
+ - Enhanced `find` for searching specific attributes.
6
+ - Added support for repository inheritance.
7
+ - `attributes_from` now copies validations as well!
8
+ - `find` now raises an exception if the arguments contain an unknown attribute key.
9
+
3
10
  ## 0.50.0 [2013-05-30]
4
11
 
5
12
  - Added `all` to repository.
data/README.md CHANGED
@@ -50,16 +50,50 @@ class User
50
50
  attribute :driver_license, String
51
51
  attribute :health_care, String
52
52
 
53
- # Nested entity composition - composing the entity with attributes from other entities
53
+ # Nested entity composition - composing the entity with attributes and validations from other entities
54
+ #
55
+ # class Job
56
+ # include Datamappify::Entity
57
+ #
58
+ # attributes :title, String
59
+ # validates :title, :presence => true
60
+ # end
61
+ #
62
+ # class User
63
+ # # ...
64
+ # attributes_from Job
65
+ # end
66
+ #
67
+ # essentially equals:
68
+ #
69
+ # class User
70
+ # # ...
71
+ # attributes :title, String
72
+ # validates :title, :presence => true
73
+ # end
54
74
  attributes_from Job
55
75
 
56
76
  # optionally you may prefix the attributes, so that:
57
77
  #
58
- # :programming
78
+ # class Hobby
79
+ # include Datamappify::Entity
80
+ #
81
+ # attributes :name, String
82
+ # validates :name, :presence => true
83
+ # end
84
+ #
85
+ # class User
86
+ # # ...
87
+ # attributes_from Hobby, :prefix_with => :hobby
88
+ # end
59
89
  #
60
90
  # becomes:
61
91
  #
62
- # :hobby_programming
92
+ # class User
93
+ # # ...
94
+ # attributes :hobby_name, String
95
+ # validates :hobby_name, :presence => true
96
+ # end
63
97
  attributes_from Hobby, :prefix_with => :hobby
64
98
 
65
99
  # Entity reference
@@ -85,6 +119,18 @@ class User
85
119
  end
86
120
  ```
87
121
 
122
+ Inheritance is supported for entities, for example:
123
+
124
+ ```ruby
125
+ class AdminUser < User
126
+ attribute :level, Integer
127
+ end
128
+
129
+ class GuestUser < User
130
+ attribute :expiry, DateTime
131
+ end
132
+ ```
133
+
88
134
  #### Lazy loading
89
135
 
90
136
  Datamappify supports attribute lazy loading via the `Lazy` module.
@@ -126,6 +172,24 @@ class UserRepository
126
172
  end
127
173
  ```
128
174
 
175
+ Inheritance is supported for repositories when your data structure is based on STI ([Single Table Inheritance](http://en.wikipedia.org/wiki/Single_Table_Inheritance)), for example:
176
+
177
+ ```ruby
178
+ class AdminUserRepository < UserRepository
179
+ for_entity AdminUser
180
+ end
181
+
182
+ class GuestUserRepository < UserRepository
183
+ for_entity GuestUser
184
+
185
+ map_attribute :expiry, 'ActiveRecord::User#expiry_date'
186
+ end
187
+ ```
188
+
189
+ In the above example, both repositories deal with the `User` data model.
190
+
191
+ ### Repository APIs
192
+
129
193
  _More repository APIs are being added, below is a list of the currently implemented APIs._
130
194
 
131
195
  #### Retrieving an entity
@@ -146,12 +210,22 @@ UserRepository.exists?(user)
146
210
 
147
211
  #### Retrieving all entities
148
212
 
149
- Returns an array of user entities.
213
+ Returns an array of entities.
150
214
 
151
215
  ```ruby
152
216
  users = UserRepository.all
153
217
  ```
154
218
 
219
+ #### Searching entities
220
+
221
+ Returns an array of entities.
222
+
223
+ ```ruby
224
+ users = UserRepository.find(:first_name => 'Fred', :driver_license => 'AABBCCDD')
225
+ ```
226
+
227
+ _Note: it does not currently support searching attributes from different data providers._
228
+
155
229
  #### Saving/updating entities
156
230
 
157
231
  Pass in an entity.
@@ -5,6 +5,34 @@ module Datamappify
5
5
  module Criteria
6
6
  module ActiveRecord
7
7
  class FindMultiple < Relational::FindMultiple
8
+ private
9
+
10
+ # @return [Array]
11
+ def records
12
+ source_class.joins(@secondaries.map(&:source_key)).where(
13
+ structured_criteria(primaries, secondaries)
14
+ )
15
+ end
16
+
17
+ # @param primaries [Array<Attribute>]
18
+ #
19
+ # @param secondaries [Array<Attribute>]
20
+ #
21
+ # @return [Hash]
22
+ def structured_criteria(primaries, secondaries)
23
+ _criteria = {}
24
+
25
+ primaries.each do |primary|
26
+ _criteria[primary.source_attribute_key] = primary.value
27
+ end
28
+
29
+ secondaries.each do |secondary|
30
+ _criteria[secondary.source_table] ||= {}
31
+ _criteria[secondary.source_table][secondary.source_attribute_key] = secondary.value
32
+ end
33
+
34
+ _criteria
35
+ end
8
36
  end
9
37
  end
10
38
  end
@@ -41,11 +41,19 @@ module Datamappify
41
41
 
42
42
  protected
43
43
 
44
+ # Name of the default source class, e.g. +"User"+,
45
+ # it is determined from either the PK or the entity
46
+ #
47
+ # @return [String]
48
+ def default_source_class_name
49
+ @default_source_class_name ||= pk ? pk.source_class_name : entity.class.name.demodulize
50
+ end
51
+
44
52
  # Key name of either the primary key (e.g. +id+) or foreign key (e.g. +user_id+)
45
53
  #
46
54
  # @return [Symbol]
47
55
  def key_name
48
- primary_record? ? :id : "#{entity.class.name.demodulize.underscore}_id".to_sym
56
+ primary_record? ? :id : any_attribute.primary_reference_key
49
57
  end
50
58
 
51
59
  # The value of {#key_name}
@@ -66,7 +74,7 @@ module Datamappify
66
74
  #
67
75
  # @return [Boolean]
68
76
  def primary_record?
69
- source_class.name.demodulize == entity.class.name.demodulize
77
+ source_class.name.demodulize == default_source_class_name
70
78
  end
71
79
 
72
80
  # Ignores the current Criteria's operation if there is no dirty attributes
@@ -80,15 +88,17 @@ module Datamappify
80
88
  #
81
89
  # @return [Hash]
82
90
  def attributes_and_values
83
- hash = {}
91
+ @attributes_and_values ||= begin
92
+ hash = {}
84
93
 
85
- attributes.each do |attribute|
86
- unless ignore_attribute?(attribute)
87
- hash[attribute.source_attribute_name] = entity.send(attribute.name)
94
+ attributes.each do |attribute|
95
+ unless ignore_attribute?(attribute)
96
+ hash[attribute.source_attribute_name] = entity.send(attribute.name)
97
+ end
88
98
  end
89
- end
90
99
 
91
- hash
100
+ hash
101
+ end
92
102
  end
93
103
 
94
104
  # Stores the attribute value in {Mapper::Attribute} for later use
@@ -100,6 +110,16 @@ module Datamappify
100
110
  end
101
111
  end
102
112
 
113
+ # @return [Attribute]
114
+ def any_attribute
115
+ @any_attribute ||= attributes.first
116
+ end
117
+
118
+ # @return [Attribute]
119
+ def pk
120
+ @pk ||= attributes.find { |attribute| attribute.key == :id }
121
+ end
122
+
103
123
  private
104
124
 
105
125
  # Ignores the attribute if it isn't dirty or if it's a primary key
@@ -5,8 +5,22 @@ module Datamappify
5
5
  class FindMultiple < Common
6
6
  alias_method :entity_class, :entity
7
7
 
8
+ attr_reader :primaries, :secondaries, :structured_criteria
9
+
10
+ def initialize(*args)
11
+ super
12
+
13
+ @primaries = []
14
+ @secondaries = []
15
+
16
+ updated_attributes.each do |attribute|
17
+ collector = attribute.primary_attribute? ? @primaries : @secondaries
18
+ collector << attribute
19
+ end
20
+ end
21
+
22
+ # @return [void]
8
23
  def perform
9
- records = source_class.where(criteria)
10
24
  records.map do |record|
11
25
  entity = entity_class.new
12
26
  update_entity(entity, record)
@@ -16,11 +30,56 @@ module Datamappify
16
30
 
17
31
  private
18
32
 
19
- def update_entity(entity, record)
33
+ # @return [Array]
34
+ def updated_attributes
35
+ unless criteria.keys & attributes.map(&:key) == criteria.keys
36
+ raise EntityAttributeInvalid
37
+ end
38
+
39
+ @updated_attributes ||= attributes.select do |attribute|
40
+ attribute.value = criteria[attribute.key]
41
+ criteria.keys.include?(attribute.key)
42
+ end
43
+ end
44
+
45
+ # @param entity [Entity]
46
+ #
47
+ # @param record [Class]
48
+ #
49
+ # @return [void]
50
+ def update_entity(entity, primary_record)
20
51
  attributes.each do |attribute|
21
- entity.send("#{attribute.name}=", record.send(attribute.source_attribute_name))
52
+ record = data_record_for(attribute, primary_record)
53
+ value = record_value_for(attribute, record)
54
+
55
+ entity.send("#{attribute.name}=", value)
56
+ end
57
+ end
58
+
59
+ # @param attribute [Attribute]
60
+ #
61
+ # @param primary_record [Class]
62
+ # an ORM model object (ActiveRecord or Sequel, etc)
63
+ #
64
+ # @return [Object]
65
+ # an ORM model object (ActiveRecord or Sequel, etc)
66
+ def data_record_for(attribute, primary_record)
67
+ if attribute.primary_attribute?
68
+ primary_record
69
+ else
70
+ primary_record.send(attribute.source_key)
22
71
  end
23
72
  end
73
+
74
+ # @param attribute [Attribute]
75
+ #
76
+ # @param record [Class]
77
+ # an ORM model object (ActiveRecord or Sequel, etc)
78
+ #
79
+ # @return [any]
80
+ def record_value_for(attribute, record)
81
+ record.nil? ? nil : record.send(attribute.source_attribute_name)
82
+ end
24
83
  end
25
84
  end
26
85
  end
@@ -5,6 +5,37 @@ module Datamappify
5
5
  module Criteria
6
6
  module Sequel
7
7
  class FindMultiple < Relational::FindMultiple
8
+ private
9
+
10
+ # @return [Array]
11
+ def records
12
+ query_builder = source_class
13
+
14
+ secondaries.each do |secondary|
15
+ query_builder = query_builder.join(secondary.source_table, secondary.primary_reference_key => :id)
16
+ end
17
+
18
+ query_builder.where(structured_criteria(primaries, secondaries))
19
+ end
20
+
21
+ # @param primaries [Array<Attribute>]
22
+ #
23
+ # @param secondaries [Array<Attribute>]
24
+ #
25
+ # @return [Hash]
26
+ def structured_criteria(primaries, secondaries)
27
+ _criteria = {}
28
+
29
+ primaries.each do |primary|
30
+ _criteria[primary.source_attribute_key] = primary.value
31
+ end
32
+
33
+ secondaries.each do |secondary|
34
+ _criteria[:"#{secondary.source_table}__#{secondary.source_attribute_name}"] = secondary.value
35
+ end
36
+
37
+ _criteria
38
+ end
8
39
  end
9
40
  end
10
41
  end
@@ -18,5 +18,8 @@ module Datamappify
18
18
 
19
19
  class EntityNotDestroyed < Error
20
20
  end
21
+
22
+ class EntityAttributeInvalid < Error
23
+ end
21
24
  end
22
25
  end
@@ -20,6 +20,9 @@ module Datamappify
20
20
  # @return [String]
21
21
  attr_reader :source_attribute_name
22
22
 
23
+ # @return [Class]
24
+ attr_reader :primary_source_class
25
+
23
26
  # @return [any]
24
27
  attr_accessor :value
25
28
 
@@ -29,23 +32,98 @@ module Datamappify
29
32
  # @param source [String]
30
33
  # data provider, class and attribute,
31
34
  # e.g. "ActiveRecord::User#surname"
32
- def initialize(name, source)
33
- @key = name
34
- @name = name.to_s
35
+ #
36
+ # @param primary_source_class [Class]
37
+ def initialize(name, source, primary_source_class)
38
+ @key = name
39
+ @name = name.to_s
40
+ @primary_source_class = primary_source_class
35
41
 
36
42
  @provider_name, @source_class_name, @source_attribute_name = parse_source(source)
43
+
44
+ unless primary_attribute? || external_attribute?
45
+ Record.build_association(self, primary_source_class)
46
+ end
37
47
  end
38
48
 
49
+ # @example
50
+ #
51
+ # UserComment
52
+ #
39
53
  # @return [Class]
40
54
  def source_class
41
55
  @source_class ||= Record.find_or_build(provider_name, source_class_name)
42
56
  end
43
57
 
58
+ # @example
59
+ #
60
+ # "user_comment"
61
+ #
62
+ # @return [String]
63
+ def source_name
64
+ @source_name ||= source_class_name.underscore
65
+ end
66
+
67
+ # @example
68
+ #
69
+ # :user_comment
70
+ #
71
+ # @return [Symbol]
72
+ def source_key
73
+ @source_key ||= source_name.to_sym
74
+ end
75
+
76
+ # @example
77
+ #
78
+ # :title
79
+ #
80
+ # @return [Symbol]
81
+ def source_attribute_key
82
+ @source_attribute_key ||= source_attribute_name.to_sym
83
+ end
84
+
85
+ # @example
86
+ #
87
+ # :user_comments
88
+ #
89
+ # @return [Symbol]
90
+ def source_table
91
+ @source_table ||= source_class_name.pluralize.underscore.to_sym
92
+ end
93
+
44
94
  # @return [Boolean]
45
95
  def primary_key?
46
96
  source_attribute_name == 'id'
47
97
  end
48
98
 
99
+ # @return [Boolean]
100
+ def primary_attribute?
101
+ provider_name == primary_provider_name && primary_source_class == source_class
102
+ end
103
+
104
+ # External attribute is from a different data provider than the primary data provider
105
+ #
106
+ # @return [Boolean]
107
+ def external_attribute?
108
+ provider_name != primary_provider_name
109
+ end
110
+
111
+ # @return [String]
112
+ def primary_provider_name
113
+ @primary_provider_name ||= primary_source_class.parent.to_s.demodulize
114
+ end
115
+
116
+ # Foreign key of the primary record, useful for joins
117
+ #
118
+ # @example
119
+ #
120
+ # :user_id
121
+ #
122
+ # @return [Symbol]
123
+ def primary_reference_key
124
+ @primary_reference_key ||= :"#{primary_source_class.to_s.demodulize.underscore}_id"
125
+ end
126
+
49
127
  private
50
128
 
51
129
  # @return [Array<String>]
@@ -10,6 +10,9 @@ module Datamappify
10
10
  # @return [String]
11
11
  attr_accessor :default_provider_name
12
12
 
13
+ # @return [String]
14
+ attr_writer :default_source_class_name
15
+
13
16
  # @return [Hash]
14
17
  # attribute name to source mapping as specified in {Repository::MappingDSL#map_attribute}
15
18
  attr_accessor :custom_mapping
@@ -24,6 +27,8 @@ module Datamappify
24
27
  @default_provider ||= Provider.const_get(default_provider_name)
25
28
  end
26
29
 
30
+ # @param provider_name [String]
31
+ #
27
32
  # @return [Module]
28
33
  def provider(provider_name)
29
34
  Provider.const_get(provider_name)
@@ -31,12 +36,12 @@ module Datamappify
31
36
 
32
37
  # @return [Class]
33
38
  def default_source_class
34
- @default_source_class ||= default_provider.find_or_build_record_class(entity_class.name)
39
+ @default_source_class ||= default_provider.find_or_build_record_class(default_source_class_name)
35
40
  end
36
41
 
37
42
  # @return [String]
38
43
  def default_source_class_name
39
- entity_class.name
44
+ @default_source_class_name ||= entity_class.name
40
45
  end
41
46
 
42
47
  # @return [Set<Attribute>]
@@ -50,11 +55,6 @@ module Datamappify
50
55
  @classified_attributes ||= Set.new(attributes).classify(&:provider_name)
51
56
  end
52
57
 
53
- # @return [Array<Attribute>]
54
- def attributes_from_default_source
55
- classified_attributes[default_provider_name].classify(&:source_class_name)[default_source_class_name]
56
- end
57
-
58
58
  private
59
59
 
60
60
  # @return [Array<Symbol>]
@@ -78,24 +78,24 @@ module Datamappify
78
78
  # @return [Array<Attribute>]
79
79
  def default_attributes
80
80
  @default_attributes ||= default_attribute_names.collect do |attribute|
81
- Attribute.new(attribute, default_source_for(attribute))
81
+ Attribute.new(attribute, default_source_for(attribute), default_source_class)
82
82
  end
83
83
  end
84
84
 
85
85
  # @return [Array<Attribute>]
86
86
  def custom_attributes
87
87
  @custom_attributes ||= custom_mapping.collect do |attribute, source|
88
- map_attribute(attribute, source)
88
+ map_custom_attribute(attribute, source)
89
89
  end
90
90
  end
91
91
 
92
92
  # @param (see Data::Mapper::Attribute#initialize)
93
93
  #
94
94
  # @return [Attribute]
95
- def map_attribute(name, source)
95
+ def map_custom_attribute(name, source)
96
96
  @custom_attribute_names << name
97
97
 
98
- Attribute.new(name, source)
98
+ Attribute.new(name, source, default_source_class)
99
99
  end
100
100
 
101
101
  # @param attribute [Symbol]
@@ -103,7 +103,7 @@ module Datamappify
103
103
  #
104
104
  # @return [String]
105
105
  def default_source_for(attribute)
106
- "#{default_provider_name}::#{entity_class.name}##{attribute}"
106
+ "#{default_provider_name}::#{default_source_class_name}##{attribute}"
107
107
  end
108
108
  end
109
109
  end
@@ -4,11 +4,26 @@ module Datamappify
4
4
  module ActiveRecord
5
5
  extend CommonProvider
6
6
 
7
- # @return [ActiveRecord::Base]
8
- def self.build_record_class(source_class_name)
9
- Datamappify::Data::Record::ActiveRecord.const_set(
10
- source_class_name, Class.new(::ActiveRecord::Base)
11
- )
7
+ class << self
8
+ # @param source_class_name (see CommonProvider::ModuleMethods#find_or_build_record_class)
9
+ #
10
+ # @return [ActiveRecord::Base]
11
+ def build_record_class(source_class_name)
12
+ Datamappify::Data::Record::ActiveRecord.const_set(
13
+ source_class_name, Class.new(::ActiveRecord::Base)
14
+ )
15
+ end
16
+
17
+ # @return [void]
18
+ def build_record_association(attribute, default_source_class)
19
+ default_source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
20
+ has_one :#{attribute.source_key}
21
+ CODE
22
+
23
+ attribute.source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
24
+ belongs_to :#{default_source_class.model_name.element}
25
+ CODE
26
+ end
12
27
  end
13
28
  end
14
29
  end
@@ -30,6 +30,8 @@ module Datamappify
30
30
 
31
31
  # Finds or builds a data record class from the data provider
32
32
  #
33
+ # @param source_class_name [String]
34
+ #
33
35
  # @return [Class]
34
36
  # the data record class
35
37
  def find_or_build_record_class(source_class_name)
@@ -4,12 +4,31 @@ module Datamappify
4
4
  module Sequel
5
5
  extend CommonProvider
6
6
 
7
- # @return [Sequel::Model]
8
- def self.build_record_class(source_class_name)
9
- Record::Sequel.const_set(
10
- source_class_name, Class.new(::Sequel::Model(source_class_name.pluralize.underscore.to_sym))
11
- ).tap do |klass|
12
- klass.raise_on_save_failure = true
7
+ class << self
8
+ # @param source_class_name (see CommonProvider::ModuleMethods#find_or_build_record_class)
9
+ #
10
+ # @return [Sequel::Model]
11
+ def build_record_class(source_class_name)
12
+ Record::Sequel.const_set(
13
+ source_class_name, Class.new(::Sequel::Model(source_class_name.pluralize.underscore.to_sym))
14
+ ).tap do |klass|
15
+ klass.raise_on_save_failure = true
16
+ end
17
+ end
18
+
19
+ # @param attribute (see Record#build_association)
20
+ #
21
+ # @param default_source_class (see Record#build_association)
22
+ #
23
+ # @return [void]
24
+ def build_record_association(attribute, default_source_class)
25
+ default_source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
26
+ one_to_one :#{attribute.source_key}
27
+ CODE
28
+
29
+ attribute.source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
30
+ one_to_one :#{default_source_class.table_name.to_s.singularize}
31
+ CODE
13
32
  end
14
33
  end
15
34
  end
@@ -2,11 +2,24 @@ module Datamappify
2
2
  module Data
3
3
  # A convenient class for finding or building a data record
4
4
  module Record
5
- # @param provider_name [String]
6
- #
7
- # @param source_class_name [String]
8
- def self.find_or_build(provider_name, source_class_name)
9
- Provider.const_get(provider_name).find_or_build_record_class(source_class_name)
5
+ class << self
6
+ # @param provider_name [String]
7
+ #
8
+ # @param source_class_name (see CommonProvider::ModuleMethods#find_or_build_record_class)
9
+ #
10
+ # @return [Object]
11
+ def find_or_build(provider_name, source_class_name)
12
+ Provider.const_get(provider_name).find_or_build_record_class(source_class_name)
13
+ end
14
+
15
+ # @param attribute [Attribute]
16
+ #
17
+ # @param default_source_class [Class]
18
+ #
19
+ # @return [void]
20
+ def build_association(attribute, default_source_class)
21
+ Provider.const_get(attribute.provider_name).build_record_association(attribute, default_source_class)
22
+ end
10
23
  end
11
24
  end
12
25
  end