datamappify 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +5 -0
- data/README.md +41 -23
- data/Rakefile +1 -1
- data/datamappify.gemspec +1 -0
- data/lib/datamappify/data/base.rb +0 -4
- data/lib/datamappify/data/errors.rb +18 -0
- data/lib/datamappify/data.rb +1 -0
- data/lib/datamappify/entity.rb +2 -31
- data/lib/datamappify/repository/attribute_source_data_class_builder.rb +23 -0
- data/lib/datamappify/repository/attributes_mapper.rb +51 -0
- data/lib/datamappify/repository/dsl.rb +13 -0
- data/lib/datamappify/repository/persistence.rb +109 -22
- data/lib/datamappify/repository.rb +15 -52
- data/lib/datamappify/version.rb +1 -1
- data/spec/entity_spec.rb +2 -46
- data/spec/repository/persistence_spec.rb +68 -0
- data/spec/repository_spec.rb +11 -31
- data/spec/spec_helper.rb +1 -1
- data/spec/support/active_record_tables.rb +18 -1
- data/spec/support/{comment.rb → entities/comment.rb} +0 -4
- data/spec/support/entities/group.rb +5 -0
- data/spec/support/{role.rb → entities/role.rb} +0 -4
- data/spec/support/entities/user.rb +18 -0
- data/spec/support/repositories/comment_repository.rb +7 -0
- data/spec/support/repositories/group_repository.rb +7 -0
- data/spec/support/repositories/role_repository.rb +7 -0
- data/spec/support/repositories/user_repository.rb +10 -0
- metadata +38 -14
- data/lib/datamappify/data/association_methods.rb +0 -29
- data/lib/datamappify/entity/associated_collection.rb +0 -28
- data/lib/datamappify/entity/associated_entity.rb +0 -28
- data/lib/datamappify/entity/association_methods.rb +0 -20
- data/spec/repository/finders_and_persistence_spec.rb +0 -71
- data/spec/support/user.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc8a8e06dc185a6fe88c046e23a6fb1a43c6cfbb
|
4
|
+
data.tar.gz: 9f54cf6189cf5f4fcf91d2bc4a3c71bfea2df191
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04f4bc8a8219f3d2c7c51dba054c374bf8045e6f8bf7a59713d0ff3a5e023e25135ad8696e7be16c3336203f6fa8721aabd529cd67088245fb4f712581472e83
|
7
|
+
data.tar.gz: 8f22447c5b5b7fd54b900e064a25cee0c320eecd9396a1339fd3179127697d6cf8d387426937d42f41a5fd3f2044ef7a92e7532c37d2e4914624f95c811b87a5
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## master
|
2
2
|
|
3
|
+
## 0.10.0 [2013-03-16]
|
4
|
+
|
5
|
+
- Refactored `Repository`. `Entity` can now have attributes sourced from different `Data` objects.
|
6
|
+
- Removed relationships support - the effort required to make it work is not worth it.
|
7
|
+
|
3
8
|
## 0.9.0 [2013-03-14]
|
4
9
|
|
5
10
|
- A total rewrite as a proof-of-concept based on the repository pattern.
|
data/README.md
CHANGED
@@ -36,26 +36,22 @@ Or install it yourself as:
|
|
36
36
|
|
37
37
|
## Usage
|
38
38
|
|
39
|
-
|
39
|
+
### Entity
|
40
40
|
|
41
41
|
```ruby
|
42
42
|
class User
|
43
43
|
include Datamappify::Entity
|
44
44
|
|
45
45
|
attribute :first_name, String
|
46
|
-
attribute :last_name,
|
47
|
-
attribute :
|
46
|
+
attribute :last_name, String
|
47
|
+
attribute :gender, String
|
48
|
+
attribute :age, Integer
|
49
|
+
attribute :passport, Integer
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# ActiveRelation collections are wrapped in the `relationships` block
|
55
|
-
relationships do
|
56
|
-
has_one :role
|
57
|
-
has_many :comments
|
58
|
-
end
|
51
|
+
validates :first_name, :presence => true,
|
52
|
+
:length => { :minimum => 2 }
|
53
|
+
validates :passport, :presence => true,
|
54
|
+
:length => { :minimum => 8 }
|
59
55
|
|
60
56
|
def full_name
|
61
57
|
"#{first_name} #{last_name}"
|
@@ -63,37 +59,59 @@ class User
|
|
63
59
|
end
|
64
60
|
```
|
65
61
|
|
66
|
-
|
62
|
+
### Repository
|
67
63
|
|
68
64
|
```ruby
|
69
|
-
|
65
|
+
class UserRepository
|
66
|
+
include Datamappify::Repository
|
67
|
+
|
68
|
+
# specify the entity class
|
69
|
+
for_entity User
|
70
|
+
|
71
|
+
# specify any attributes that need to be mapped
|
72
|
+
#
|
73
|
+
# for example:
|
74
|
+
# - 'gender' is mapped to the 'User' ActiveRecord class and its 'sex' attribute
|
75
|
+
# - 'passport' is mapped to the 'UserPassport' ActiveRecord class and its 'number' attribute
|
76
|
+
# - attributes not specified here are mapped automatically (in this case, 'User')
|
77
|
+
map_attribute :gender, 'User#sex'
|
78
|
+
map_attribute :passport, 'UserPassport#number'
|
79
|
+
end
|
80
|
+
|
81
|
+
user_repository = UserRepository.instance
|
70
82
|
```
|
71
83
|
|
72
|
-
Retrieving
|
84
|
+
#### Retrieving an entity
|
73
85
|
|
74
86
|
```ruby
|
75
87
|
user = user_repository.find(1)
|
76
88
|
```
|
77
89
|
|
78
|
-
Saving/updating
|
90
|
+
#### Saving/updating an entity
|
79
91
|
|
80
92
|
```ruby
|
81
93
|
user_repository.save(user)
|
82
94
|
```
|
83
95
|
|
84
|
-
Destroying
|
96
|
+
#### Destroying an entity
|
97
|
+
|
98
|
+
Note that due to the attributes mapping, any data found in mapped ActiveRecord objects are not touched.
|
85
99
|
|
86
100
|
```ruby
|
87
101
|
user_repository.destroy(user)
|
88
102
|
```
|
89
103
|
|
104
|
+
## Changelog
|
105
|
+
|
106
|
+
Refer to [CHANGELOG](CHANGELOG.md).
|
107
|
+
|
90
108
|
## Todo
|
91
109
|
|
92
|
-
-
|
93
|
-
-
|
94
|
-
-
|
95
|
-
- Support for
|
96
|
-
-
|
110
|
+
- Perform `save` in a transaction.
|
111
|
+
- Hooks for persistence (`before_save` and `after_save`, etc).
|
112
|
+
- Track dirty entity attributes to avoid unnecessary DB queries.
|
113
|
+
- Support for configurable primary keys and foreign keys.
|
114
|
+
- Entity should dictate Data, so schema and migrations should be automatically generated.
|
97
115
|
|
98
116
|
## Similar Projects
|
99
117
|
|
data/Rakefile
CHANGED
data/datamappify.gemspec
CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "rake"
|
27
27
|
spec.add_development_dependency "minitest"
|
28
28
|
spec.add_development_dependency "minitest-colorize"
|
29
|
+
spec.add_development_dependency "m"
|
29
30
|
spec.add_development_dependency "pry"
|
30
31
|
spec.add_development_dependency "simplecov"
|
31
32
|
spec.add_development_dependency "cane"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Datamappify
|
2
|
+
module Data
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class EntityInvalid < Error
|
7
|
+
def initialize(entity)
|
8
|
+
super entity.errors.full_messages.join(', ')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class EntityNotSaved < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class EntityNotDestroyed < Error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/datamappify/data.rb
CHANGED
data/lib/datamappify/entity.rb
CHANGED
@@ -1,43 +1,14 @@
|
|
1
1
|
require 'virtus'
|
2
|
-
require 'datamappify/entity/association_methods'
|
3
2
|
|
4
3
|
module Datamappify
|
5
4
|
module Entity
|
6
5
|
def self.included(klass)
|
7
6
|
klass.class_eval do
|
8
7
|
include Virtus
|
8
|
+
include Virtus::Equalizer.new(inspect)
|
9
|
+
include ActiveModel::Validations
|
9
10
|
|
10
11
|
attribute :id, Integer
|
11
|
-
|
12
|
-
extend Behaviour
|
13
|
-
extend Structure
|
14
|
-
extend AssociationMethods
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
module Behaviour
|
19
|
-
def self.extended(klass)
|
20
|
-
klass.class_eval do
|
21
|
-
include ActiveModel::Validations
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
attr_accessor :stored_validations
|
26
|
-
|
27
|
-
def validations(&block)
|
28
|
-
self.stored_validations = block
|
29
|
-
|
30
|
-
instance_eval(&block)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
module Structure
|
35
|
-
attr_accessor :stored_relationships
|
36
|
-
|
37
|
-
def relationships(&block)
|
38
|
-
self.stored_relationships = block
|
39
|
-
|
40
|
-
instance_eval(&block)
|
41
12
|
end
|
42
13
|
end
|
43
14
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Datamappify
|
2
|
+
module Repository
|
3
|
+
class AttributeSourceDataClassBuilder
|
4
|
+
class << self
|
5
|
+
def build(data_class_name, data_fields_mapping)
|
6
|
+
@data_class_name = data_class_name
|
7
|
+
|
8
|
+
build_data_class unless data_class_is_defined?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def build_data_class
|
14
|
+
Data.const_set(@data_class_name, Class.new(Data::Base))
|
15
|
+
end
|
16
|
+
|
17
|
+
def data_class_is_defined?
|
18
|
+
Data.const_defined?(@data_class_name, false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'datamappify/repository/attribute_source_data_class_builder'
|
2
|
+
|
3
|
+
module Datamappify
|
4
|
+
module Repository
|
5
|
+
class AttributesMapper
|
6
|
+
def initialize(repository)
|
7
|
+
@repository = repository
|
8
|
+
|
9
|
+
map_entity_attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_data_classes
|
13
|
+
@repository.data_mapping.each do |data_class_name, data_fields_mapping|
|
14
|
+
AttributeSourceDataClassBuilder.build(data_class_name, data_fields_mapping)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def map_entity_attributes
|
21
|
+
map_non_custom_entity_attributes
|
22
|
+
map_custom_entity_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
def map_non_custom_entity_attributes
|
26
|
+
@repository.data_mapping[@repository.entity_class.name] = {}
|
27
|
+
|
28
|
+
non_custom_attributes.each do |attribute_name|
|
29
|
+
@repository.data_mapping[@repository.entity_class.name][attribute_name] = attribute_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def non_custom_attributes
|
34
|
+
@repository.entity_class.attribute_set.entries.map(&:name) - @repository.custom_attributes_mapping.keys
|
35
|
+
end
|
36
|
+
|
37
|
+
def map_custom_entity_attributes
|
38
|
+
@repository.custom_attributes_mapping.each do |attribute_name, source|
|
39
|
+
map_custom_entity_attribute(attribute_name, source)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def map_custom_entity_attribute(attribute_name, source)
|
44
|
+
data_class_name, data_field_name = source.split('#')
|
45
|
+
|
46
|
+
@repository.data_mapping[data_class_name] ||= {}
|
47
|
+
@repository.data_mapping[data_class_name][data_field_name.to_sym] = attribute_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,37 +1,124 @@
|
|
1
1
|
module Datamappify
|
2
|
-
|
2
|
+
module Repository
|
3
3
|
module Persistence
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
def find(id_or_ids)
|
5
|
+
if id_or_ids.is_a?(Array)
|
6
|
+
find_many(id_or_ids)
|
7
|
+
else
|
8
|
+
find_one(id_or_ids)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(entity)
|
13
|
+
create_or_update(entity)
|
14
|
+
rescue Datamappify::Data::EntityInvalid
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def save!(entity)
|
19
|
+
save(entity) || raise(Datamappify::Data::EntityNotSaved)
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy(id_or_entity)
|
23
|
+
default_data_class.send :destroy, extract_entity_id(id_or_entity)
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy!(id_or_entity)
|
27
|
+
destroy(id_or_entity) || raise(Datamappify::Data::EntityNotDestroyed)
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(symbol, *args)
|
31
|
+
default_data_class.send symbol, *args
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def find_many(ids)
|
37
|
+
ids.map { |id| find_one(id) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_one(id)
|
41
|
+
entity_class.new data_mapping_walker(data_mapping, id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_or_update(entity)
|
45
|
+
raise Datamappify::Data::EntityInvalid.new(entity) if entity.invalid?
|
46
|
+
|
47
|
+
entity.id ? update(entity) : create(entity)
|
48
|
+
end
|
49
|
+
|
50
|
+
def create(entity)
|
51
|
+
entity_class.new data_mapping_walker(data_mapping, nil, entity.attributes)
|
52
|
+
end
|
53
|
+
|
54
|
+
def update(entity)
|
55
|
+
entity_class.new data_mapping_walker(data_mapping, entity.id, entity.attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def data_mapping_walker(data_mapping, id, updated_attributes = nil)
|
59
|
+
composed_attributes = {}
|
60
|
+
|
61
|
+
data_mapping.each do |data_class_name, data_fields_mapping|
|
62
|
+
id = find_data_object_id(data_class_name, id)
|
63
|
+
values = extract_data_field_values(data_class_name, id, updated_attributes, data_fields_mapping)
|
64
|
+
|
65
|
+
data_fields_with_values = {}
|
66
|
+
|
67
|
+
data_fields_mapping.each_with_index do |(data_field_name, attribute_name), index|
|
68
|
+
composed_attributes[attribute_name] = data_fields_with_values[data_field_name] = values[index]
|
14
69
|
end
|
70
|
+
|
71
|
+
if id && updated_attributes
|
72
|
+
update_data_object(data_class_name, id, data_fields_with_values)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
composed_attributes
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_data_class
|
80
|
+
data_class(entity_class.name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def data_class(data_class_name)
|
84
|
+
"Datamappify::Data::#{data_class_name}".constantize
|
85
|
+
end
|
86
|
+
|
87
|
+
def foreign_key_field_name
|
88
|
+
"#{entity_class.name.underscore}_id"
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_data_object_id(data_class_name, id)
|
92
|
+
if entity_class.name == data_class_name
|
93
|
+
id
|
94
|
+
else
|
95
|
+
data_class(data_class_name).where(foreign_key_field_name => id).pluck(:id).first
|
15
96
|
end
|
16
97
|
end
|
17
98
|
|
18
|
-
|
19
|
-
|
20
|
-
|
99
|
+
def extract_data_field_values(data_class_name, id, updated_attributes, data_fields_mapping)
|
100
|
+
if updated_attributes.nil?
|
101
|
+
find_data_field_values_from_db(data_class_name, id, data_fields_mapping.keys)
|
102
|
+
else
|
103
|
+
find_data_field_values_from_attributes(updated_attributes, data_fields_mapping.values)
|
21
104
|
end
|
22
105
|
end
|
23
106
|
|
24
|
-
|
107
|
+
def find_data_field_values_from_db(data_class_name, id, data_field_names)
|
108
|
+
data_class(data_class_name).where(:id => id).pluck(*data_field_names).flatten
|
109
|
+
end
|
110
|
+
|
111
|
+
def find_data_field_values_from_attributes(attributes, attribute_names)
|
112
|
+
attribute_names.map { |name| attributes[name] }
|
113
|
+
end
|
25
114
|
|
26
|
-
def
|
27
|
-
data_object = data_class.
|
28
|
-
data_object.
|
115
|
+
def update_data_object(data_class_name, id, data_fields_with_values)
|
116
|
+
data_object = data_class(data_class_name).find_or_initialize_by(:id => id)
|
117
|
+
data_object.update_attributes data_fields_with_values
|
29
118
|
end
|
30
119
|
|
31
|
-
def
|
32
|
-
|
33
|
-
data_object.update_attributes(entity.attributes)
|
34
|
-
data_object.send(save_method_name) && data_object.entity
|
120
|
+
def extract_entity_id(id_or_entity)
|
121
|
+
id_or_entity.is_a?(Integer) ? id_or_entity : id_or_entity.id
|
35
122
|
end
|
36
123
|
end
|
37
124
|
end
|
@@ -1,65 +1,28 @@
|
|
1
|
+
require 'singleton'
|
1
2
|
require 'datamappify/repository/persistence'
|
3
|
+
require 'datamappify/repository/dsl'
|
4
|
+
require 'datamappify/repository/attributes_mapper'
|
2
5
|
|
3
6
|
module Datamappify
|
4
|
-
|
7
|
+
module Repository
|
5
8
|
include Persistence
|
6
9
|
|
7
|
-
def
|
8
|
-
|
10
|
+
def self.included(klass)
|
11
|
+
klass.class_eval do
|
12
|
+
mattr_accessor :entity_class
|
13
|
+
mattr_accessor :custom_attributes_mapping
|
14
|
+
mattr_accessor :data_mapping
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
apply_relationships_to_data_class
|
13
|
-
add_entity_to_data_class
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def find(entity_or_id)
|
18
|
-
data_class.find(extract_entity_id(entity_or_id))
|
19
|
-
end
|
20
|
-
|
21
|
-
def method_missing(symbol, *args)
|
22
|
-
data_class.send symbol, *args
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def data_class_is_defined?
|
28
|
-
Data.const_defined?(@entity_class.name, false)
|
29
|
-
end
|
30
|
-
|
31
|
-
def apply_validations_to_data_class
|
32
|
-
if @entity_class.stored_validations
|
33
|
-
data_class.class_eval(&@entity_class.stored_validations)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def apply_relationships_to_data_class
|
38
|
-
if @entity_class.stored_relationships
|
39
|
-
data_class.class_eval(&@entity_class.stored_relationships)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def add_entity_to_data_class
|
44
|
-
data_class.class_eval <<-CODE
|
45
|
-
def entity
|
46
|
-
#{@entity_class.name}.new(attributes)
|
47
|
-
end
|
48
|
-
CODE
|
49
|
-
end
|
16
|
+
klass.custom_attributes_mapping = {}
|
17
|
+
klass.data_mapping = {}
|
50
18
|
|
51
|
-
|
52
|
-
|
53
|
-
if data_class_is_defined?
|
54
|
-
Data.const_get(@entity_class.name, false)
|
55
|
-
else
|
56
|
-
Data.const_set(@entity_class.name, Class.new(Data::Base))
|
57
|
-
end
|
19
|
+
include Singleton
|
20
|
+
extend DSL
|
58
21
|
end
|
59
22
|
end
|
60
23
|
|
61
|
-
def
|
62
|
-
|
24
|
+
def initialize
|
25
|
+
AttributesMapper.new(self).build_data_classes
|
63
26
|
end
|
64
27
|
end
|
65
28
|
end
|
data/lib/datamappify/version.rb
CHANGED
data/spec/entity_spec.rb
CHANGED
@@ -27,57 +27,13 @@ describe Datamappify::Entity do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
describe "validations" do
|
30
|
-
it "
|
31
|
-
user.first_name = nil
|
30
|
+
it "validates attributes" do
|
32
31
|
user.valid?.must_equal false
|
33
32
|
|
34
33
|
user.first_name = 'Fred'
|
34
|
+
user.passport = 'FREDWU42'
|
35
35
|
user.valid?.must_equal true
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
describe "relationships" do
|
40
|
-
describe "getters" do
|
41
|
-
it "belongs_to" do
|
42
|
-
comment.user.must_be_kind_of User
|
43
|
-
end
|
44
|
-
|
45
|
-
it "has_one" do
|
46
|
-
user.role.must_be_kind_of Role
|
47
|
-
end
|
48
|
-
|
49
|
-
it "has_many" do
|
50
|
-
user.comments.must_be_kind_of Array
|
51
|
-
user.comments.must_be_empty
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe "setters" do
|
56
|
-
let(:entity) { Object.new }
|
57
|
-
|
58
|
-
it "belongs_to" do
|
59
|
-
comment.user = entity
|
60
|
-
comment.user.must_be_kind_of Object
|
61
|
-
comment.user.must_equal entity
|
62
|
-
end
|
63
|
-
|
64
|
-
it "has_one" do
|
65
|
-
user.role = entity
|
66
|
-
user.role.must_be_kind_of Object
|
67
|
-
user.role.must_equal entity
|
68
|
-
end
|
69
|
-
|
70
|
-
it "has_many" do
|
71
|
-
user.comments << entity
|
72
|
-
user.comments << entity
|
73
|
-
user.comments.must_be_kind_of Array
|
74
|
-
user.comments.count.must_equal 2
|
75
|
-
user.comments[0].must_equal entity
|
76
|
-
|
77
|
-
user.comments = [entity]
|
78
|
-
user.comments.count.must_equal 1
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
38
|
end
|
83
39
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Datamappify::Repository do
|
4
|
+
let(:user_repository) { UserRepository.instance }
|
5
|
+
let(:user) { User.new(:id => 1, :first_name => 'Fred', :passport => 'FREDWU42') }
|
6
|
+
let(:user_valid) { User.new(:first_name => 'Batman', :passport => 'ARKHAMCITY') }
|
7
|
+
let(:user_invalid) { User.new(:first_name => 'a') }
|
8
|
+
|
9
|
+
let(:has_db_user) do
|
10
|
+
Datamappify::Data::User.create!(:first_name => 'Fred')
|
11
|
+
Datamappify::Data::UserPassport.create!(:number => 'FREDWU42', :user_id => 1)
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
user_repository
|
16
|
+
end
|
17
|
+
|
18
|
+
it "#find" do
|
19
|
+
has_db_user
|
20
|
+
|
21
|
+
user_repository.find(1).must_equal user
|
22
|
+
user_repository.find([1]).must_equal [user]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "#save success" do
|
26
|
+
new_user = user_repository.save(user_valid)
|
27
|
+
new_user.must_be_kind_of User
|
28
|
+
new_user.first_name.must_equal 'Batman'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "#save! failure" do
|
32
|
+
-> { user_repository.save!(user_invalid) }.must_raise Datamappify::Data::EntityNotSaved
|
33
|
+
end
|
34
|
+
|
35
|
+
it "updates an existing record" do
|
36
|
+
has_db_user
|
37
|
+
|
38
|
+
user.first_name = 'Steve'
|
39
|
+
user.gender = 'male'
|
40
|
+
user.passport = 'LOCOMOTE'
|
41
|
+
|
42
|
+
updated_user = user_repository.save(user)
|
43
|
+
updated_user.first_name.must_equal 'Steve'
|
44
|
+
updated_user.gender.must_equal 'male'
|
45
|
+
updated_user.passport.must_equal 'LOCOMOTE'
|
46
|
+
|
47
|
+
persisted_user = user_repository.find(user.id)
|
48
|
+
persisted_user.first_name.must_equal 'Steve'
|
49
|
+
persisted_user.gender.must_equal 'male'
|
50
|
+
persisted_user.passport.must_equal 'LOCOMOTE'
|
51
|
+
|
52
|
+
user_repository.count.must_equal 1
|
53
|
+
end
|
54
|
+
|
55
|
+
it "#destroy via id" do
|
56
|
+
has_db_user
|
57
|
+
|
58
|
+
user_repository.destroy!(1)
|
59
|
+
user_repository.count.must_equal 0
|
60
|
+
end
|
61
|
+
|
62
|
+
it "#destroy via entity" do
|
63
|
+
has_db_user
|
64
|
+
|
65
|
+
user_repository.destroy!(user)
|
66
|
+
user_repository.count.must_equal 0
|
67
|
+
end
|
68
|
+
end
|
data/spec/repository_spec.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
3
|
describe Datamappify::Repository do
|
4
|
-
let(:user_repository) {
|
5
|
-
let(:comment_repository) {
|
6
|
-
let(:role_repository) {
|
4
|
+
let(:user_repository) { UserRepository.instance }
|
5
|
+
let(:comment_repository) { CommentRepository.instance }
|
6
|
+
let(:role_repository) { RoleRepository.instance }
|
7
|
+
let(:group_repository) { GroupRepository.instance }
|
8
|
+
|
9
|
+
before do
|
10
|
+
user_repository
|
11
|
+
comment_repository
|
12
|
+
role_repository
|
13
|
+
group_repository
|
14
|
+
end
|
7
15
|
|
8
16
|
describe "ActiveRecord data objects" do
|
9
17
|
it "defines the Data::User class after the repository is initialised" do
|
10
|
-
user_repository
|
11
18
|
Datamappify::Data.const_defined?(:User, false).must_equal true
|
12
19
|
end
|
13
20
|
|
14
21
|
describe "data objects" do
|
15
22
|
subject { Datamappify::Data::User }
|
16
23
|
|
17
|
-
before do
|
18
|
-
user_repository
|
19
|
-
end
|
20
|
-
|
21
24
|
it "inherites from Datamappify::Data::Base" do
|
22
25
|
subject.superclass.must_equal Datamappify::Data::Base
|
23
26
|
subject.ancestors.must_include ActiveRecord::Base
|
@@ -26,29 +29,6 @@ describe Datamappify::Repository do
|
|
26
29
|
it "has 'users' as the table name" do
|
27
30
|
subject.table_name.must_equal 'users'
|
28
31
|
end
|
29
|
-
|
30
|
-
describe "relationships" do
|
31
|
-
let(:user) { Datamappify::Data::User.new }
|
32
|
-
let(:comment) { Datamappify::Data::Comment.new }
|
33
|
-
let(:role) { Datamappify::Data::Role.new }
|
34
|
-
|
35
|
-
it "belongs_to" do
|
36
|
-
assert_correct_associated_data_class_name(comment, :user, 'Datamappify::Data::User')
|
37
|
-
comment.user.must_be_nil
|
38
|
-
end
|
39
|
-
|
40
|
-
it "has_one" do
|
41
|
-
assert_correct_associated_data_class_name(user, :role, 'Datamappify::Data::Role')
|
42
|
-
user.role.must_be_nil
|
43
|
-
end
|
44
|
-
|
45
|
-
it "has_many" do
|
46
|
-
assert_correct_associated_data_class_name(user, :comments, 'Datamappify::Data::Comment')
|
47
|
-
assert_correct_associated_data_class_name(role, :users, 'Datamappify::Data::User')
|
48
|
-
user.comments.must_be_empty
|
49
|
-
role.users.must_be_empty
|
50
|
-
end
|
51
|
-
end
|
52
32
|
end
|
53
33
|
|
54
34
|
def assert_correct_associated_data_class_name(klass, association_name, data_class_name)
|
data/spec/spec_helper.rb
CHANGED
@@ -13,7 +13,7 @@ end
|
|
13
13
|
|
14
14
|
require File.expand_path('../../lib/datamappify', __FILE__)
|
15
15
|
|
16
|
-
Dir[File.expand_path('../support
|
16
|
+
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f }
|
17
17
|
|
18
18
|
DatabaseCleaner.strategy = :truncation
|
19
19
|
|
@@ -15,6 +15,7 @@ ActiveRecord::Migration.suppress_messages do
|
|
15
15
|
create_table :users do |t|
|
16
16
|
t.string :first_name, :null => false
|
17
17
|
t.string :last_name
|
18
|
+
t.string :sex
|
18
19
|
t.integer :age
|
19
20
|
t.references :role
|
20
21
|
t.timestamps
|
@@ -22,7 +23,7 @@ ActiveRecord::Migration.suppress_messages do
|
|
22
23
|
|
23
24
|
create_table :comments do |t|
|
24
25
|
t.string :content
|
25
|
-
t.
|
26
|
+
t.belongs_to :user
|
26
27
|
t.timestamps
|
27
28
|
end
|
28
29
|
|
@@ -30,5 +31,21 @@ ActiveRecord::Migration.suppress_messages do
|
|
30
31
|
t.string :name
|
31
32
|
t.timestamps
|
32
33
|
end
|
34
|
+
|
35
|
+
create_table :groups do |t|
|
36
|
+
t.string :name
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :groups_users do |t|
|
41
|
+
t.belongs_to :user
|
42
|
+
t.belongs_to :group
|
43
|
+
end
|
44
|
+
|
45
|
+
create_table :user_passports do |t|
|
46
|
+
t.string :number
|
47
|
+
t.belongs_to :user
|
48
|
+
t.timestamps
|
49
|
+
end
|
33
50
|
end
|
34
51
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class User
|
2
|
+
include Datamappify::Entity
|
3
|
+
|
4
|
+
attribute :first_name, String
|
5
|
+
attribute :last_name, String
|
6
|
+
attribute :gender, String
|
7
|
+
attribute :age, Integer
|
8
|
+
attribute :passport, Integer
|
9
|
+
|
10
|
+
validates :first_name, :presence => true,
|
11
|
+
:length => { :minimum => 2 }
|
12
|
+
validates :passport, :presence => true,
|
13
|
+
:length => { :minimum => 8 }
|
14
|
+
|
15
|
+
def full_name
|
16
|
+
"#{first_name} #{last_name}"
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datamappify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fred Wu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-03-
|
11
|
+
date: 2013-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: virtus
|
@@ -120,6 +120,20 @@ dependencies:
|
|
120
120
|
- - '>='
|
121
121
|
- !ruby/object:Gem::Version
|
122
122
|
version: '0'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: m
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
123
137
|
- !ruby/object:Gem::Dependency
|
124
138
|
name: pry
|
125
139
|
requirement: !ruby/object:Gem::Requirement
|
@@ -212,23 +226,28 @@ files:
|
|
212
226
|
- datamappify.gemspec
|
213
227
|
- lib/datamappify.rb
|
214
228
|
- lib/datamappify/data.rb
|
215
|
-
- lib/datamappify/data/association_methods.rb
|
216
229
|
- lib/datamappify/data/base.rb
|
230
|
+
- lib/datamappify/data/errors.rb
|
217
231
|
- lib/datamappify/entity.rb
|
218
|
-
- lib/datamappify/entity/associated_collection.rb
|
219
|
-
- lib/datamappify/entity/associated_entity.rb
|
220
|
-
- lib/datamappify/entity/association_methods.rb
|
221
232
|
- lib/datamappify/repository.rb
|
233
|
+
- lib/datamappify/repository/attribute_source_data_class_builder.rb
|
234
|
+
- lib/datamappify/repository/attributes_mapper.rb
|
235
|
+
- lib/datamappify/repository/dsl.rb
|
222
236
|
- lib/datamappify/repository/persistence.rb
|
223
237
|
- lib/datamappify/version.rb
|
224
238
|
- spec/entity_spec.rb
|
225
|
-
- spec/repository/
|
239
|
+
- spec/repository/persistence_spec.rb
|
226
240
|
- spec/repository_spec.rb
|
227
241
|
- spec/spec_helper.rb
|
228
242
|
- spec/support/active_record_tables.rb
|
229
|
-
- spec/support/comment.rb
|
230
|
-
- spec/support/
|
231
|
-
- spec/support/
|
243
|
+
- spec/support/entities/comment.rb
|
244
|
+
- spec/support/entities/group.rb
|
245
|
+
- spec/support/entities/role.rb
|
246
|
+
- spec/support/entities/user.rb
|
247
|
+
- spec/support/repositories/comment_repository.rb
|
248
|
+
- spec/support/repositories/group_repository.rb
|
249
|
+
- spec/support/repositories/role_repository.rb
|
250
|
+
- spec/support/repositories/user_repository.rb
|
232
251
|
homepage: https://github.com/fredwu/datamappify
|
233
252
|
licenses:
|
234
253
|
- MIT
|
@@ -255,10 +274,15 @@ specification_version: 4
|
|
255
274
|
summary: Separate domain logic from data persistence.
|
256
275
|
test_files:
|
257
276
|
- spec/entity_spec.rb
|
258
|
-
- spec/repository/
|
277
|
+
- spec/repository/persistence_spec.rb
|
259
278
|
- spec/repository_spec.rb
|
260
279
|
- spec/spec_helper.rb
|
261
280
|
- spec/support/active_record_tables.rb
|
262
|
-
- spec/support/comment.rb
|
263
|
-
- spec/support/
|
264
|
-
- spec/support/
|
281
|
+
- spec/support/entities/comment.rb
|
282
|
+
- spec/support/entities/group.rb
|
283
|
+
- spec/support/entities/role.rb
|
284
|
+
- spec/support/entities/user.rb
|
285
|
+
- spec/support/repositories/comment_repository.rb
|
286
|
+
- spec/support/repositories/group_repository.rb
|
287
|
+
- spec/support/repositories/role_repository.rb
|
288
|
+
- spec/support/repositories/user_repository.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Datamappify
|
2
|
-
module Data
|
3
|
-
module AssociationMethods
|
4
|
-
def belongs_to(name, scope = nil, options = {})
|
5
|
-
build_associated_repository(name)
|
6
|
-
|
7
|
-
super(name, scope = nil, options)
|
8
|
-
end
|
9
|
-
|
10
|
-
def has_one(name, scope = nil, options = {})
|
11
|
-
build_associated_repository(name)
|
12
|
-
|
13
|
-
super(name, scope = nil, options)
|
14
|
-
end
|
15
|
-
|
16
|
-
def has_many(name, scope = nil, options = {}, &extension)
|
17
|
-
build_associated_repository(name)
|
18
|
-
|
19
|
-
super(name, scope = nil, options, &extension)
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def build_associated_repository(entity_or_collection_name)
|
25
|
-
Datamappify::Repository.new(entity_or_collection_name.to_s.classify.constantize)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Datamappify
|
2
|
-
module Entity
|
3
|
-
class AssociatedCollection
|
4
|
-
def initialize(context, name)
|
5
|
-
build_collection_getter(context, name)
|
6
|
-
build_collection_setter(context, name)
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def build_collection_getter(context, name)
|
12
|
-
context.class_eval <<-CODE
|
13
|
-
def #{name}
|
14
|
-
@#{name} ||= []
|
15
|
-
end
|
16
|
-
CODE
|
17
|
-
end
|
18
|
-
|
19
|
-
def build_collection_setter(context, name)
|
20
|
-
context.class_eval <<-CODE
|
21
|
-
def #{name}=(collection)
|
22
|
-
@#{name} = collection
|
23
|
-
end
|
24
|
-
CODE
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Datamappify
|
2
|
-
module Entity
|
3
|
-
class AssociatedEntity
|
4
|
-
def initialize(context, name)
|
5
|
-
build_entity_getter(context, name)
|
6
|
-
build_entity_setter(context, name)
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def build_entity_getter(context, name)
|
12
|
-
context.class_eval <<-CODE
|
13
|
-
def #{name}
|
14
|
-
@#{name} ||= "#{name}".classify.constantize.new
|
15
|
-
end
|
16
|
-
CODE
|
17
|
-
end
|
18
|
-
|
19
|
-
def build_entity_setter(context, name)
|
20
|
-
context.class_eval <<-CODE
|
21
|
-
def #{name}=(entity)
|
22
|
-
@#{name} = entity
|
23
|
-
end
|
24
|
-
CODE
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'datamappify/entity/associated_entity'
|
2
|
-
require 'datamappify/entity/associated_collection'
|
3
|
-
|
4
|
-
module Datamappify
|
5
|
-
module Entity
|
6
|
-
module AssociationMethods
|
7
|
-
def belongs_to(name, *args)
|
8
|
-
AssociatedEntity.new(self, name)
|
9
|
-
end
|
10
|
-
|
11
|
-
def has_one(name, *args)
|
12
|
-
AssociatedEntity.new(self, name)
|
13
|
-
end
|
14
|
-
|
15
|
-
def has_many(name, *args)
|
16
|
-
AssociatedCollection.new(self, name)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Datamappify::Repository do
|
4
|
-
let(:user_repository) { Datamappify::Repository.new(User) }
|
5
|
-
let(:user) { user_repository.first }
|
6
|
-
let(:user_valid) { User.new(:first_name => 'Batman') }
|
7
|
-
let(:user_invalid) { User.new(:first_name => 'a') }
|
8
|
-
|
9
|
-
before do
|
10
|
-
user_repository
|
11
|
-
Datamappify::Data::User.create!(:first_name => 'Fred')
|
12
|
-
end
|
13
|
-
|
14
|
-
describe "single record" do
|
15
|
-
it "#find via id" do
|
16
|
-
user_repository.find(1).must_equal user
|
17
|
-
end
|
18
|
-
|
19
|
-
it "#find via entity" do
|
20
|
-
user_repository.find(user.entity).must_equal user
|
21
|
-
end
|
22
|
-
|
23
|
-
it "#save success" do
|
24
|
-
new_user = user_repository.save(user_valid)
|
25
|
-
new_user.must_be_kind_of User
|
26
|
-
new_user.first_name.must_equal 'Batman'
|
27
|
-
end
|
28
|
-
|
29
|
-
it "#save! failure" do
|
30
|
-
-> { user_repository.save!(user_invalid) }.must_raise ActiveRecord::RecordInvalid
|
31
|
-
end
|
32
|
-
|
33
|
-
it "updates an existing record" do
|
34
|
-
user.first_name = 'Steve'
|
35
|
-
changed_user = User.new(user.attributes)
|
36
|
-
user_repository.save(changed_user).first_name.must_equal 'Steve'
|
37
|
-
user_repository.count.must_equal 1
|
38
|
-
end
|
39
|
-
|
40
|
-
it "#destroy via id" do
|
41
|
-
user_repository.destroy(1)
|
42
|
-
user_repository.count.must_equal 0
|
43
|
-
end
|
44
|
-
|
45
|
-
it "#destroy via entity" do
|
46
|
-
user_repository.destroy(user.entity)
|
47
|
-
user_repository.count.must_equal 0
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe "collection records" do
|
52
|
-
it "#find" do
|
53
|
-
user_repository.all.must_be_kind_of ActiveRecord::Relation::ActiveRecord_Relation_Datamappify_Data_User
|
54
|
-
user_repository.count.must_equal 1
|
55
|
-
end
|
56
|
-
|
57
|
-
it "#save" do
|
58
|
-
new_user_entity = user.entity
|
59
|
-
new_user_entity.id = nil
|
60
|
-
|
61
|
-
new_users = user_repository.save([new_user_entity, user_valid])
|
62
|
-
new_users[0].first_name.must_equal 'Fred'
|
63
|
-
new_users[1].first_name.must_equal 'Batman'
|
64
|
-
|
65
|
-
user_repository.count.must_equal 3
|
66
|
-
user_repository.all[0].first_name.must_equal 'Fred'
|
67
|
-
user_repository.all[1].first_name.must_equal 'Fred'
|
68
|
-
user_repository.all[2].first_name.must_equal 'Batman'
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
data/spec/support/user.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
class User
|
2
|
-
include Datamappify::Entity
|
3
|
-
|
4
|
-
attribute :first_name, String
|
5
|
-
attribute :last_name, String
|
6
|
-
attribute :age, Integer
|
7
|
-
|
8
|
-
validations do
|
9
|
-
validates :first_name, :presence => true,
|
10
|
-
:length => { :minimum => 2 }
|
11
|
-
end
|
12
|
-
|
13
|
-
relationships do
|
14
|
-
has_one :role
|
15
|
-
has_many :comments
|
16
|
-
end
|
17
|
-
|
18
|
-
def full_name
|
19
|
-
"#{first_name} #{last_name}"
|
20
|
-
end
|
21
|
-
end
|