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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +41 -23
  5. data/Rakefile +1 -1
  6. data/datamappify.gemspec +1 -0
  7. data/lib/datamappify/data/base.rb +0 -4
  8. data/lib/datamappify/data/errors.rb +18 -0
  9. data/lib/datamappify/data.rb +1 -0
  10. data/lib/datamappify/entity.rb +2 -31
  11. data/lib/datamappify/repository/attribute_source_data_class_builder.rb +23 -0
  12. data/lib/datamappify/repository/attributes_mapper.rb +51 -0
  13. data/lib/datamappify/repository/dsl.rb +13 -0
  14. data/lib/datamappify/repository/persistence.rb +109 -22
  15. data/lib/datamappify/repository.rb +15 -52
  16. data/lib/datamappify/version.rb +1 -1
  17. data/spec/entity_spec.rb +2 -46
  18. data/spec/repository/persistence_spec.rb +68 -0
  19. data/spec/repository_spec.rb +11 -31
  20. data/spec/spec_helper.rb +1 -1
  21. data/spec/support/active_record_tables.rb +18 -1
  22. data/spec/support/{comment.rb → entities/comment.rb} +0 -4
  23. data/spec/support/entities/group.rb +5 -0
  24. data/spec/support/{role.rb → entities/role.rb} +0 -4
  25. data/spec/support/entities/user.rb +18 -0
  26. data/spec/support/repositories/comment_repository.rb +7 -0
  27. data/spec/support/repositories/group_repository.rb +7 -0
  28. data/spec/support/repositories/role_repository.rb +7 -0
  29. data/spec/support/repositories/user_repository.rb +10 -0
  30. metadata +38 -14
  31. data/lib/datamappify/data/association_methods.rb +0 -29
  32. data/lib/datamappify/entity/associated_collection.rb +0 -28
  33. data/lib/datamappify/entity/associated_entity.rb +0 -28
  34. data/lib/datamappify/entity/association_methods.rb +0 -20
  35. data/spec/repository/finders_and_persistence_spec.rb +0 -71
  36. data/spec/support/user.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5cd51d61440490e0e15894ce7243cb4c324945b5
4
- data.tar.gz: cbbf430016eaf1c88b181806e3901a009ce9028f
3
+ metadata.gz: bc8a8e06dc185a6fe88c046e23a6fb1a43c6cfbb
4
+ data.tar.gz: 9f54cf6189cf5f4fcf91d2bc4a3c71bfea2df191
5
5
  SHA512:
6
- metadata.gz: 55ecbac18bf660597c7494a12b9adf88a1597202388ae131305653c2e5902d4d455d284b37bc1ebd0e4bec82b408ea4e778198df07e4c819c264af34658a3200
7
- data.tar.gz: 5bbc77d5ef8a8522e66461bead4a94490932138464f27d912593d8bf5ef26414bb26712c63b2f2ce234ed48b99f1fe37d5ee58959057c94f471799983282c2b8
6
+ metadata.gz: 04f4bc8a8219f3d2c7c51dba054c374bf8045e6f8bf7a59713d0ff3a5e023e25135ad8696e7be16c3336203f6fa8721aabd529cd67088245fb4f712581472e83
7
+ data.tar.gz: 8f22447c5b5b7fd54b900e064a25cee0c320eecd9396a1339fd3179127697d6cf8d387426937d42f41a5fd3f2044ef7a92e7532c37d2e4914624f95c811b87a5
data/.travis.yml CHANGED
@@ -2,3 +2,5 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ script:
6
+ - bundle exec rake --trace
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
- Model:
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, String
47
- attribute :age, Integer
46
+ attribute :last_name, String
47
+ attribute :gender, String
48
+ attribute :age, Integer
49
+ attribute :passport, Integer
48
50
 
49
- # ActiveModel::Validations rules are wrapped in the `validations` block
50
- validations do
51
- validates :first_name, :presence => true
52
- end
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
- Corresponding repository:
62
+ ### Repository
67
63
 
68
64
  ```ruby
69
- user_repository = Datamappify::Repository.new(User)
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 records:
84
+ #### Retrieving an entity
73
85
 
74
86
  ```ruby
75
87
  user = user_repository.find(1)
76
88
  ```
77
89
 
78
- Saving/updating a record:
90
+ #### Saving/updating an entity
79
91
 
80
92
  ```ruby
81
93
  user_repository.save(user)
82
94
  ```
83
95
 
84
- Destroying a record:
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
- - Entity should dictate Data, so schema and migrations should be automatically generated
93
- - Support for `set_primary_key` (currently the PK is hard coded to `id`)
94
- - Support for HABTM association type
95
- - Support for entity attribute and data column mapping
96
- - Repository should handle asscociated data
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
@@ -12,7 +12,7 @@ begin
12
12
 
13
13
  desc "Run cane to check quality metrics"
14
14
  Cane::RakeTask.new(:quality) do |cane|
15
- cane.abc_max = 8
15
+ cane.abc_max = 10
16
16
  cane.no_doc = true
17
17
  cane.style_glob = './lib/**/*.rb'
18
18
  cane.style_measure = 120
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"
@@ -1,10 +1,6 @@
1
- require 'datamappify/data/association_methods'
2
-
3
1
  module Datamappify
4
2
  module Data
5
3
  class Base < ActiveRecord::Base
6
- extend Data::AssociationMethods
7
-
8
4
  self.abstract_class = true
9
5
  end
10
6
  end
@@ -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
@@ -1,3 +1,4 @@
1
+ require 'datamappify/data/errors'
1
2
  require 'datamappify/data/base'
2
3
 
3
4
  module Datamappify
@@ -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
@@ -0,0 +1,13 @@
1
+ module Datamappify
2
+ module Repository
3
+ module DSL
4
+ def for_entity(entity_class)
5
+ self.entity_class = entity_class
6
+ end
7
+
8
+ def map_attribute(attribute, source)
9
+ self.custom_attributes_mapping[attribute] = source
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,37 +1,124 @@
1
1
  module Datamappify
2
- class Repository
2
+ module Repository
3
3
  module Persistence
4
- [:save, :save!].each do |save_method_name|
5
- define_method save_method_name do |entity_or_collection|
6
- entity = collection = entity_or_collection
7
-
8
- if collection.is_a?(Array)
9
- collection.map { |e| send(save_method_name, e) }
10
- elsif entity.id
11
- update_data_record(save_method_name, entity)
12
- else
13
- create_data_record(save_method_name, entity)
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
- [:destroy, :destroy!].each do |destroy_method_name|
19
- define_method destroy_method_name do |entity_or_id|
20
- data_class.send(destroy_method_name, extract_entity_id(entity_or_id))
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
- private
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 create_data_record(save_method_name, entity)
27
- data_object = data_class.new(entity.attributes)
28
- data_object.send(save_method_name) && data_object.entity
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 update_data_record(save_method_name, entity)
32
- data_object = data_class.find(entity.id)
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
- class Repository
7
+ module Repository
5
8
  include Persistence
6
9
 
7
- def initialize(entity_class)
8
- @entity_class = entity_class
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
- unless data_class_is_defined?
11
- apply_validations_to_data_class
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
- def data_class
52
- @data_class ||= begin
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 extract_entity_id(entity_or_id)
62
- entity_or_id.is_a?(Integer) ? entity_or_id : entity_or_id.id
24
+ def initialize
25
+ AttributesMapper.new(self).build_data_classes
63
26
  end
64
27
  end
65
28
  end
@@ -1,3 +1,3 @@
1
1
  module Datamappify
2
- VERSION = '0.9.0'
2
+ VERSION = '0.10.0'
3
3
  end
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 "requires first name" do
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
@@ -1,23 +1,26 @@
1
1
  require_relative 'spec_helper'
2
2
 
3
3
  describe Datamappify::Repository do
4
- let(:user_repository) { Datamappify::Repository.new(User) }
5
- let(:comment_repository) { Datamappify::Repository.new(Comment) }
6
- let(:role_repository) { Datamappify::Repository.new(Role) }
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/**/*', __FILE__)].each { |f| require f }
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.references :user
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
@@ -2,8 +2,4 @@ class Comment
2
2
  include Datamappify::Entity
3
3
 
4
4
  attribute :content, String
5
-
6
- relationships do
7
- belongs_to :user
8
- end
9
5
  end
@@ -0,0 +1,5 @@
1
+ class Group
2
+ include Datamappify::Entity
3
+
4
+ attribute :name, String
5
+ end
@@ -2,8 +2,4 @@ class Role
2
2
  include Datamappify::Entity
3
3
 
4
4
  attribute :name, String
5
-
6
- relationships do
7
- has_many :users
8
- end
9
5
  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
@@ -0,0 +1,7 @@
1
+ require_relative '../entities/comment'
2
+
3
+ class CommentRepository
4
+ include Datamappify::Repository
5
+
6
+ for_entity Comment
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../entities/group'
2
+
3
+ class GroupRepository
4
+ include Datamappify::Repository
5
+
6
+ for_entity Group
7
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../entities/role'
2
+
3
+ class RoleRepository
4
+ include Datamappify::Repository
5
+
6
+ for_entity Role
7
+ end
@@ -0,0 +1,10 @@
1
+ require_relative '../entities/user'
2
+
3
+ class UserRepository
4
+ include Datamappify::Repository
5
+
6
+ for_entity User
7
+
8
+ map_attribute :gender, 'User#sex'
9
+ map_attribute :passport, 'UserPassport#number'
10
+ 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.9.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-14 00:00:00.000000000 Z
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/finders_and_persistence_spec.rb
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/role.rb
231
- - spec/support/user.rb
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/finders_and_persistence_spec.rb
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/role.rb
264
- - spec/support/user.rb
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