datamappify 0.9.0 → 0.10.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 +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
|