datamappify 0.51.1 → 0.52.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +44 -17
- data/datamappify.gemspec +2 -2
- data/lib/datamappify/data/criteria/active_record/save.rb +5 -1
- data/lib/datamappify/data/criteria/common.rb +26 -20
- data/lib/datamappify/data/criteria/concerns/update_primary_record.rb +28 -0
- data/lib/datamappify/data/criteria/relational/concerns/set_criteria.rb +36 -0
- data/lib/datamappify/data/criteria/relational/find.rb +2 -0
- data/lib/datamappify/data/criteria/relational/find_by_key.rb +4 -3
- data/lib/datamappify/data/criteria/relational/save.rb +2 -0
- data/lib/datamappify/data/criteria/relational/save_by_key.rb +5 -3
- data/lib/datamappify/data/criteria/sequel/save.rb +5 -1
- data/lib/datamappify/data/mapper/attribute.rb +38 -17
- data/lib/datamappify/data/mapper.rb +7 -5
- data/lib/datamappify/data/provider/active_record.rb +7 -0
- data/lib/datamappify/data/provider/sequel.rb +8 -1
- data/lib/datamappify/data/record.rb +4 -0
- data/lib/datamappify/entity/composable/attribute.rb +16 -0
- data/lib/datamappify/entity/composable/attributes.rb +54 -0
- data/lib/datamappify/entity/composable/validators.rb +63 -0
- data/lib/datamappify/entity/composable.rb +6 -87
- data/lib/datamappify/entity.rb +2 -2
- data/lib/datamappify/repository/mapping_dsl.rb +2 -2
- data/lib/datamappify/repository/query_method/method.rb +15 -4
- data/lib/datamappify/version.rb +1 -1
- data/spec/entity/composable_spec.rb +3 -3
- data/spec/repository/reverse_mapped_spec.rb +56 -0
- data/spec/support/entities/author.rb +6 -0
- data/spec/support/entities/computer_hardware.rb +2 -2
- data/spec/support/entities/post.rb +9 -0
- data/spec/support/repositories/active_record/author_repository.rb +6 -0
- data/spec/support/repositories/active_record/post_repository.rb +9 -0
- data/spec/support/repositories/sequel/author_repository.rb +6 -0
- data/spec/support/repositories/sequel/post_repository.rb +9 -0
- data/spec/support/tables/active_record.rb +13 -0
- data/spec/support/tables/sequel.rb +17 -0
- data/spec/unit/entity/composable_spec.rb +3 -3
- metadata +35 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd079889034877268b854c9c1402e747eb84348a
|
4
|
+
data.tar.gz: ea1988a5cea43748d9d55658e999b9c1a95a9de6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18fde8f5ddccee08904440fdd706f0563130a78566a8860bc3a67017b35320444655db5226e6019d73e29e069750a80ddc267dd7e0bcc63d64a84daf05d263c8
|
7
|
+
data.tar.gz: 91fa0a11fd1af61e398b0b22e6dcad429c4123653dafabcb9c70bc39681ce0a8a9b1e61944996f688eac8221b771344a02679f9b137d3cc278a57a4b6b9114f6
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
# Datamappify [![Gem Version](https://badge.fury.io/rb/datamappify.png)](http://badge.fury.io/rb/datamappify) [![Build Status](https://api.travis-ci.org/fredwu/datamappify.png?branch=master)](http://travis-ci.org/fredwu/datamappify) [![Coverage Status](https://coveralls.io/repos/fredwu/datamappify/badge.png)](https://coveralls.io/r/fredwu/datamappify) [![Code Climate](https://codeclimate.com/github/fredwu/datamappify.png)](https://codeclimate.com/github/fredwu/datamappify)
|
2
2
|
|
3
|
+
#### Compose, decouple and manage domain logic and data persistence separately. Works great with forms!
|
4
|
+
|
3
5
|
## Overview
|
4
6
|
|
5
|
-
|
7
|
+
The typical Rails (and ActiveRecord) way of building applications is great for small to medium sized projects, but when projects grow larger and more complex, your models too become larger and more complex - it is not uncommon to have god classes such as a User model.
|
8
|
+
|
9
|
+
Datamappify tries to solve two common problems in web applications:
|
10
|
+
|
11
|
+
1. The coupling between domain logic and data persistence.
|
12
|
+
2. The coupling between forms and models.
|
6
13
|
|
7
|
-
Datamappify is built
|
14
|
+
Datamappify is loosely based on the [Repository Pattern](http://martinfowler.com/eaaCatalog/repository.html) and [Entity Aggregation](http://msdn.microsoft.com/en-au/library/ff649505.aspx), and is built on top of [Virtus](https://github.com/solnic/virtus) and existing ORMs (ActiveRecord and Sequel, etc).
|
8
15
|
|
9
|
-
|
16
|
+
There are three main design goals:
|
17
|
+
|
18
|
+
1. To utilise the powerfulness of existing ORMs so that using Datamappify doesn't interrupt too much of your current workflow. For example, [Devise](https://github.com/plataformatec/devise) would still work if you use it with a `UserAccount` ActiveRecord model that is attached to a `User` entity managed by Datamappify.
|
19
|
+
2. To have a flexible entity model that works great with dealing with form data. For example, [SimpleForm](https://github.com/plataformatec/simple_form) would still work with nested attributes from different ORM models if you map entity attributes smartly in your repositories managed by Datamappify.
|
20
|
+
3. To have a set of data providers to encapsulate the handling of how the data is persisted. This is especially useful for dealing with external data sources such as a web service. For example, by calling `UserRepository.save(user)`, certain attributes of the user entity are now persisted on a remote web service. Better yet, dirty tracking and lazy loading are supported out of the box!
|
10
21
|
|
11
22
|
Datamappify consists of three components:
|
12
23
|
|
13
24
|
- __Entity__ contains models behaviour, think an ActiveRecord model with the persistence specifics removed.
|
14
25
|
- __Repository__ is responsible for data retrieval and persistence, e.g. `find`, `save` and `destroy`, etc.
|
15
|
-
- __Data__ as the name suggests, holds your model data. It contains ORM objects (ActiveRecord
|
26
|
+
- __Data__ as the name suggests, holds your model data. It contains ORM objects (e.g. ActiveRecord models).
|
16
27
|
|
17
28
|
Below is a high level and somewhat simplified overview of Datamappify's architecture.
|
18
29
|
|
@@ -39,6 +50,10 @@ Add this line to your application's Gemfile:
|
|
39
50
|
|
40
51
|
Entity uses [Virtus](https://github.com/solnic/virtus) DSL for defining attributes and [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations.html) DSL for validations.
|
41
52
|
|
53
|
+
The cool thing about Virtus is that all your attributes get [coercion](https://github.com/solnic/virtus#collection-member-coercions) for free!
|
54
|
+
|
55
|
+
Below is an example of a User entity, with inline comments on how some of the DSLs work.
|
56
|
+
|
42
57
|
```ruby
|
43
58
|
class User
|
44
59
|
include Datamappify::Entity
|
@@ -142,11 +157,13 @@ class User
|
|
142
157
|
end
|
143
158
|
```
|
144
159
|
|
145
|
-
When an entity is lazy loaded, only attributes from the
|
160
|
+
When an entity is lazy loaded, only attributes from the primary source (e.g. `User` entity's primary source would be `ActiveRecord::User` as specified in the corresponding repository) will be loaded. Other attributes will only be loaded once they are called. This is especially useful if some of your data sources are external web services.
|
146
161
|
|
147
162
|
### Repository
|
148
163
|
|
149
|
-
|
164
|
+
Repository maps entity attributes to DB columns - better yet, you can even map attributes to __different ORMs__!
|
165
|
+
|
166
|
+
Below is an example of a repository for the User entity, you can have more than one repositories for the same entity.
|
150
167
|
|
151
168
|
```ruby
|
152
169
|
class UserRepository
|
@@ -160,6 +177,8 @@ class UserRepository
|
|
160
177
|
|
161
178
|
# specify any attributes that need to be mapped
|
162
179
|
#
|
180
|
+
# for attributes mapped from a different source class, a foreign key on the source class is required
|
181
|
+
#
|
163
182
|
# for example:
|
164
183
|
# - 'last_name' is mapped to the 'User' ActiveRecord class and its 'surname' attribute
|
165
184
|
# - 'driver_license' is mapped to the 'UserDriverLicense' ActiveRecord class and its 'number' attribute
|
@@ -169,6 +188,14 @@ class UserRepository
|
|
169
188
|
map_attribute :driver_license, 'ActiveRecord::UserDriverLicense#number'
|
170
189
|
map_attribute :passport, 'Sequel::UserPassport#number'
|
171
190
|
map_attribute :health_care, 'Sequel::UserHealthCare#number'
|
191
|
+
|
192
|
+
# attributes can also be reverse mapped by specifying the `via` option
|
193
|
+
#
|
194
|
+
# for example, the below attribute will look for `hobby_id` on the user object,
|
195
|
+
# and map `hobby_name` from the `name` attribute of `ActiveRecord::Hobby`
|
196
|
+
#
|
197
|
+
# this is useful for mapping form fields (similar to ActiveRecord's nested attributes)
|
198
|
+
map_attribute :hobby_name, 'ActiveRecord::Hobby#name', :via => :hobby_id
|
172
199
|
end
|
173
200
|
```
|
174
201
|
|
@@ -186,7 +213,7 @@ class GuestUserRepository < UserRepository
|
|
186
213
|
end
|
187
214
|
```
|
188
215
|
|
189
|
-
In the above example, both repositories deal with the `User` data model.
|
216
|
+
In the above example, both repositories deal with the `ActiveRecord::User` data model.
|
190
217
|
|
191
218
|
### Repository APIs
|
192
219
|
|
@@ -194,7 +221,7 @@ _More repository APIs are being added, below is a list of the currently implemen
|
|
194
221
|
|
195
222
|
#### Retrieving an entity
|
196
223
|
|
197
|
-
|
224
|
+
Accepts an id.
|
198
225
|
|
199
226
|
```ruby
|
200
227
|
user = UserRepository.find(1)
|
@@ -202,7 +229,7 @@ user = UserRepository.find(1)
|
|
202
229
|
|
203
230
|
#### Checking if an entity exists in the repository
|
204
231
|
|
205
|
-
|
232
|
+
Accepts an entity.
|
206
233
|
|
207
234
|
```ruby
|
208
235
|
UserRepository.exists?(user)
|
@@ -228,7 +255,7 @@ _Note: it does not currently support searching attributes from different data pr
|
|
228
255
|
|
229
256
|
#### Saving/updating entities
|
230
257
|
|
231
|
-
|
258
|
+
Accepts an entity.
|
232
259
|
|
233
260
|
There is also `save!` that raises `Datamappify::Data::EntityNotSaved`.
|
234
261
|
|
@@ -240,10 +267,10 @@ Datamappify supports attribute dirty tracking - only dirty attributes will be sa
|
|
240
267
|
|
241
268
|
##### Mark attributes as dirty
|
242
269
|
|
243
|
-
Sometimes it's useful to manually mark the whole entity, or some attributes in the entity to be dirty
|
270
|
+
Sometimes it's useful to manually mark the whole entity, or some attributes in the entity to be dirty. In this case, you could:
|
244
271
|
|
245
272
|
```ruby
|
246
|
-
UserRepository.states.mark_as_dirty(user)
|
273
|
+
UserRepository.states.mark_as_dirty(user) # marks the whole entity as dirty
|
247
274
|
|
248
275
|
UserRepository.states.find(user).changed? #=> true
|
249
276
|
UserRepository.states.find(user).first_name_changed? #=> true
|
@@ -254,7 +281,7 @@ UserRepository.states.find(user).age_changed? #=> true
|
|
254
281
|
Or:
|
255
282
|
|
256
283
|
```ruby
|
257
|
-
UserRepository.states.mark_as_dirty(user, :first_name, :last_name)
|
284
|
+
UserRepository.states.mark_as_dirty(user, :first_name, :last_name) # marks only first_name and last_name as dirty
|
258
285
|
|
259
286
|
UserRepository.states.find(user).changed? #=> true
|
260
287
|
UserRepository.states.find(user).first_name_changed? #=> true
|
@@ -264,11 +291,11 @@ UserRepository.states.find(user).age_changed? #=> false
|
|
264
291
|
|
265
292
|
#### Destroying an entity
|
266
293
|
|
267
|
-
|
294
|
+
Accepts an entity.
|
268
295
|
|
269
296
|
There is also `destroy!` that raises `Datamappify::Data::EntityNotDestroyed`.
|
270
297
|
|
271
|
-
Note that due to the attributes mapping, any data found in mapped
|
298
|
+
Note that due to the attributes mapping, any data found in mapped records are not touched. For example the corresponding `ActiveRecord::User` record will be destroyed, but `ActiveRecord::Hobby` that is associated will not.
|
272
299
|
|
273
300
|
```ruby
|
274
301
|
UserRepository.destroy(user)
|
@@ -287,7 +314,7 @@ Datamappify supports the following callbacks via [Hooks](https://github.com/apot
|
|
287
314
|
- after_save
|
288
315
|
- after_destroy
|
289
316
|
|
290
|
-
Callbacks are defined in repositories, and they have access to the entity.
|
317
|
+
Callbacks are defined in repositories, and they have access to the entity. For example:
|
291
318
|
|
292
319
|
```ruby
|
293
320
|
class UserRepository
|
@@ -315,7 +342,7 @@ class UserRepository
|
|
315
342
|
end
|
316
343
|
```
|
317
344
|
|
318
|
-
Note: Returning either `nil` or `false` from the callback will cancel all subsequent callbacks (and the action itself,
|
345
|
+
Note: Returning either `nil` or `false` from the callback will cancel all subsequent callbacks (and the action itself, if it's a `before_` callback).
|
319
346
|
|
320
347
|
## Changelog
|
321
348
|
|
data/datamappify.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Datamappify::VERSION
|
9
9
|
spec.authors = ["Fred Wu"]
|
10
10
|
spec.email = ["ifredwu@gmail.com"]
|
11
|
-
spec.description = %q{Compose and manage domain logic and data persistence separately
|
11
|
+
spec.description = %q{Compose, decouple and manage domain logic and data persistence separately. Works great with forms!}
|
12
12
|
spec.summary = spec.description
|
13
13
|
spec.homepage = "https://github.com/fredwu/datamappify"
|
14
14
|
spec.license = "MIT"
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "virtus", "
|
21
|
+
spec.add_dependency "virtus", ">= 1.0.0.beta0", "<= 2.0"
|
22
22
|
spec.add_dependency "activemodel", ">= 4.0.0.rc1", "< 5"
|
23
23
|
spec.add_dependency "hooks", "~> 0.3.0"
|
24
24
|
|
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'datamappify/data/criteria/relational/save'
|
2
|
+
require 'datamappify/data/criteria/concerns/update_primary_record'
|
2
3
|
|
3
4
|
module Datamappify
|
4
5
|
module Data
|
5
6
|
module Criteria
|
6
7
|
module ActiveRecord
|
7
8
|
class Save < Relational::Save
|
9
|
+
include Concerns::UpdatePrimaryRecord
|
10
|
+
|
8
11
|
private
|
9
12
|
|
10
13
|
def save_record
|
@@ -14,7 +17,8 @@ module Datamappify
|
|
14
17
|
|
15
18
|
def save(record)
|
16
19
|
record.update_attributes attributes_and_values
|
17
|
-
|
20
|
+
|
21
|
+
super
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -11,11 +11,17 @@ module Datamappify
|
|
11
11
|
attr_reader :entity
|
12
12
|
|
13
13
|
# @return [void]
|
14
|
-
|
14
|
+
attr_accessor :criteria
|
15
15
|
|
16
16
|
# @return [Set<Mapper::Attribute>]
|
17
17
|
attr_reader :attributes
|
18
18
|
|
19
|
+
# @return [Hash]
|
20
|
+
attr_accessor :attributes_and_values
|
21
|
+
|
22
|
+
# @return [Hash]
|
23
|
+
attr_reader :options
|
24
|
+
|
19
25
|
# @param source_class [Class]
|
20
26
|
#
|
21
27
|
# @param args [any]
|
@@ -24,7 +30,7 @@ module Datamappify
|
|
24
30
|
# an optional block
|
25
31
|
def initialize(source_class, *args, &block)
|
26
32
|
@source_class = source_class
|
27
|
-
@entity, @criteria, @attributes = *args
|
33
|
+
@entity, @criteria, @attributes, @options = *args
|
28
34
|
@block = block
|
29
35
|
end
|
30
36
|
|
@@ -39,6 +45,23 @@ module Datamappify
|
|
39
45
|
result
|
40
46
|
end
|
41
47
|
|
48
|
+
# Attributes with their corresponding values
|
49
|
+
#
|
50
|
+
# @return [Hash]
|
51
|
+
def attributes_and_values
|
52
|
+
@attributes_and_values ||= begin
|
53
|
+
hash = {}
|
54
|
+
|
55
|
+
attributes.each do |attribute|
|
56
|
+
unless ignore_attribute?(attribute)
|
57
|
+
hash[attribute.source_attribute_name] = entity.send(attribute.name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
hash
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
42
65
|
protected
|
43
66
|
|
44
67
|
# Name of the default source class, e.g. +"User"+,
|
@@ -84,23 +107,6 @@ module Datamappify
|
|
84
107
|
attributes_and_values.empty?
|
85
108
|
end
|
86
109
|
|
87
|
-
# Attributes with their corresponding values
|
88
|
-
#
|
89
|
-
# @return [Hash]
|
90
|
-
def attributes_and_values
|
91
|
-
@attributes_and_values ||= begin
|
92
|
-
hash = {}
|
93
|
-
|
94
|
-
attributes.each do |attribute|
|
95
|
-
unless ignore_attribute?(attribute)
|
96
|
-
hash[attribute.source_attribute_name] = entity.send(attribute.name)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
hash
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
110
|
# Stores the attribute value in {Mapper::Attribute} for later use
|
105
111
|
#
|
106
112
|
# @return [void]
|
@@ -117,7 +123,7 @@ module Datamappify
|
|
117
123
|
|
118
124
|
# @return [Attribute]
|
119
125
|
def pk
|
120
|
-
@pk ||= attributes.find
|
126
|
+
@pk ||= attributes.find(&:primary_key?)
|
121
127
|
end
|
122
128
|
|
123
129
|
private
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Datamappify
|
2
|
+
module Data
|
3
|
+
module Criteria
|
4
|
+
module Concerns
|
5
|
+
module UpdatePrimaryRecord
|
6
|
+
private
|
7
|
+
|
8
|
+
def save(record)
|
9
|
+
if options && options[:via] && options[:primary_record]
|
10
|
+
update_primary_record_with(record)
|
11
|
+
end
|
12
|
+
|
13
|
+
record
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_primary_record_with(record)
|
17
|
+
save = self.class.superclass.new(options[:primary_record].class, entity, {
|
18
|
+
:id => options[:primary_record].id
|
19
|
+
})
|
20
|
+
|
21
|
+
save.attributes_and_values = { options[:via] => record.id }
|
22
|
+
save.send(:save_record)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Datamappify
|
2
|
+
module Data
|
3
|
+
module Criteria
|
4
|
+
module Relational
|
5
|
+
module Concerns
|
6
|
+
module SetCriteria
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
|
10
|
+
set_criteria if entity.id
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def set_criteria
|
16
|
+
self.criteria = if options[:via]
|
17
|
+
criteria_for_reverse_mapping
|
18
|
+
else
|
19
|
+
criteria_for_normal_mapping
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def criteria_for_reverse_mapping
|
24
|
+
reverse_id = options[:primary_record].send(options[:via])
|
25
|
+
reverse_id ? { :id => reverse_id } : {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def criteria_for_normal_mapping
|
29
|
+
{ key_name => entity.id }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
require 'datamappify/data/criteria/relational/find'
|
2
|
+
require 'datamappify/data/criteria/relational/concerns/set_criteria'
|
2
3
|
|
3
4
|
module Datamappify
|
4
5
|
module Data
|
5
6
|
module Criteria
|
6
7
|
module Relational
|
7
8
|
class FindByKey < Find
|
8
|
-
|
9
|
-
super(source_class, entity, nil, attributes, &block)
|
9
|
+
include Concerns::SetCriteria
|
10
10
|
|
11
|
-
|
11
|
+
def initialize(source_class, entity, attributes, options = {}, &block)
|
12
|
+
super(source_class, entity, nil, attributes, options, &block)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -1,12 +1,14 @@
|
|
1
|
+
require 'datamappify/data/criteria/relational/concerns/set_criteria'
|
2
|
+
|
1
3
|
module Datamappify
|
2
4
|
module Data
|
3
5
|
module Criteria
|
4
6
|
module Relational
|
5
7
|
module SaveByKey
|
6
|
-
|
7
|
-
super(source_class, entity, {}, attributes, &block)
|
8
|
+
include Concerns::SetCriteria
|
8
9
|
|
9
|
-
|
10
|
+
def initialize(source_class, entity, attributes, options = {}, &block)
|
11
|
+
super(source_class, entity, {}, attributes, options, &block)
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'datamappify/data/criteria/relational/save'
|
2
|
+
require 'datamappify/data/criteria/concerns/update_primary_record'
|
2
3
|
|
3
4
|
module Datamappify
|
4
5
|
module Data
|
5
6
|
module Criteria
|
6
7
|
module Sequel
|
7
8
|
class Save < Relational::Save
|
9
|
+
include Concerns::UpdatePrimaryRecord
|
10
|
+
|
8
11
|
private
|
9
12
|
|
10
13
|
def save_record
|
@@ -14,7 +17,8 @@ module Datamappify
|
|
14
17
|
|
15
18
|
def save(record)
|
16
19
|
record.update attributes_and_values
|
17
|
-
|
20
|
+
|
21
|
+
super
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -23,6 +23,9 @@ module Datamappify
|
|
23
23
|
# @return [Class]
|
24
24
|
attr_reader :primary_source_class
|
25
25
|
|
26
|
+
# @return [Hash]
|
27
|
+
attr_reader :options
|
28
|
+
|
26
29
|
# @return [any]
|
27
30
|
attr_accessor :value
|
28
31
|
|
@@ -33,16 +36,21 @@ module Datamappify
|
|
33
36
|
# data provider, class and attribute,
|
34
37
|
# e.g. "ActiveRecord::User#surname"
|
35
38
|
#
|
36
|
-
# @param
|
37
|
-
def initialize(name, source,
|
39
|
+
# @param options [Hash]
|
40
|
+
def initialize(name, source, options)
|
38
41
|
@key = name
|
39
42
|
@name = name.to_s
|
40
|
-
@
|
43
|
+
@options = options
|
44
|
+
@primary_source_class = options[:primary_source_class]
|
41
45
|
|
42
46
|
@provider_name, @source_class_name, @source_attribute_name = parse_source(source)
|
43
47
|
|
44
|
-
|
45
|
-
|
48
|
+
if secondary_attribute?
|
49
|
+
if reverse_mapped?
|
50
|
+
Record.build_reversed_association(self, primary_source_class)
|
51
|
+
else
|
52
|
+
Record.build_association(self, primary_source_class)
|
53
|
+
end
|
46
54
|
end
|
47
55
|
end
|
48
56
|
|
@@ -96,18 +104,6 @@ module Datamappify
|
|
96
104
|
source_attribute_name == 'id'
|
97
105
|
end
|
98
106
|
|
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
107
|
# @return [String]
|
112
108
|
def primary_provider_name
|
113
109
|
@primary_provider_name ||= primary_source_class.parent.to_s.demodulize
|
@@ -124,6 +120,31 @@ module Datamappify
|
|
124
120
|
@primary_reference_key ||= :"#{primary_source_class.to_s.demodulize.underscore}_id"
|
125
121
|
end
|
126
122
|
|
123
|
+
# Primary attribute is from the same data provider and the same source class
|
124
|
+
#
|
125
|
+
# @return [Boolean]
|
126
|
+
def primary_attribute?
|
127
|
+
provider_name == primary_provider_name && primary_source_class == source_class
|
128
|
+
end
|
129
|
+
|
130
|
+
# Secondary attribute is from the same data provider but a different source class
|
131
|
+
#
|
132
|
+
# @return [Boolean]
|
133
|
+
def secondary_attribute?
|
134
|
+
provider_name == primary_provider_name && primary_source_class != source_class
|
135
|
+
end
|
136
|
+
|
137
|
+
# External attribute is from a different data provider than the primary data provider
|
138
|
+
#
|
139
|
+
# @return [Boolean]
|
140
|
+
def external_attribute?
|
141
|
+
provider_name != primary_provider_name
|
142
|
+
end
|
143
|
+
|
144
|
+
def reverse_mapped?
|
145
|
+
@options[:via]
|
146
|
+
end
|
147
|
+
|
127
148
|
private
|
128
149
|
|
129
150
|
# @return [Array<String>]
|
@@ -78,24 +78,26 @@ 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), default_source_class)
|
81
|
+
Attribute.new(attribute, default_source_for(attribute), :primary_source_class => default_source_class)
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
85
|
# @return [Array<Attribute>]
|
86
86
|
def custom_attributes
|
87
|
-
@custom_attributes ||= custom_mapping.collect do |attribute,
|
88
|
-
map_custom_attribute(attribute,
|
87
|
+
@custom_attributes ||= custom_mapping.collect do |attribute, source_and_options|
|
88
|
+
map_custom_attribute(attribute, *source_and_options)
|
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_custom_attribute(name, source)
|
95
|
+
def map_custom_attribute(name, source, options)
|
96
96
|
@custom_attribute_names << name
|
97
97
|
|
98
|
-
|
98
|
+
options.merge!(:primary_source_class => default_source_class)
|
99
|
+
|
100
|
+
Attribute.new(name, source, options)
|
99
101
|
end
|
100
102
|
|
101
103
|
# @param attribute [Symbol]
|
@@ -24,6 +24,13 @@ module Datamappify
|
|
24
24
|
belongs_to :#{default_source_class.model_name.element}
|
25
25
|
CODE
|
26
26
|
end
|
27
|
+
|
28
|
+
# @return [void]
|
29
|
+
def build_record_reversed_association(attribute, default_source_class)
|
30
|
+
default_source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
31
|
+
belongs_to :#{attribute.source_key}
|
32
|
+
CODE
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
29
36
|
end
|
@@ -27,7 +27,14 @@ module Datamappify
|
|
27
27
|
CODE
|
28
28
|
|
29
29
|
attribute.source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
30
|
-
|
30
|
+
many_to_one :#{default_source_class.table_name.to_s.singularize}
|
31
|
+
CODE
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [void]
|
35
|
+
def build_record_reversed_association(attribute, default_source_class)
|
36
|
+
default_source_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
37
|
+
many_to_one :#{attribute.source_key}
|
31
38
|
CODE
|
32
39
|
end
|
33
40
|
end
|
@@ -20,6 +20,10 @@ module Datamappify
|
|
20
20
|
def build_association(attribute, default_source_class)
|
21
21
|
Provider.const_get(attribute.provider_name).build_record_association(attribute, default_source_class)
|
22
22
|
end
|
23
|
+
|
24
|
+
def build_reversed_association(attribute, default_source_class)
|
25
|
+
Provider.const_get(attribute.provider_name).build_record_reversed_association(attribute, default_source_class)
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Datamappify
|
2
|
+
module Entity
|
3
|
+
module Composable
|
4
|
+
class Attribute
|
5
|
+
# @param name [Virtus::Attribute]
|
6
|
+
#
|
7
|
+
# @param prefix [Symbol]
|
8
|
+
#
|
9
|
+
# @return [void]
|
10
|
+
def self.prefix(name, prefix)
|
11
|
+
:"#{prefix}_#{name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|