datamappify 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.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