datamappify 0.70.0.beta1 → 0.70.0.beta2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +55 -3
  4. data/datamappify.gemspec +4 -2
  5. data/lib/datamappify/data/criteria/active_record/limit.rb +7 -1
  6. data/lib/datamappify/data/criteria/active_record/save.rb +7 -1
  7. data/lib/datamappify/data/criteria/common.rb +4 -4
  8. data/lib/datamappify/data/criteria/relational/count.rb +4 -2
  9. data/lib/datamappify/data/criteria/sequel/limit.rb +4 -1
  10. data/lib/datamappify/data/criteria/sequel/save.rb +7 -1
  11. data/lib/datamappify/entity/association.rb +11 -0
  12. data/lib/datamappify/entity/association/validation.rb +1 -1
  13. data/lib/datamappify/entity/compatibility/active_record.rb +13 -0
  14. data/lib/datamappify/entity/compatibility/association/active_record.rb +19 -3
  15. data/lib/datamappify/entity/composable/attributes.rb +1 -1
  16. data/lib/datamappify/extensions/kaminari/collection_methods.rb +16 -0
  17. data/lib/datamappify/extensions/kaminari/criteria_processor.rb +36 -0
  18. data/lib/datamappify/lazy/attributes_handler.rb +2 -2
  19. data/lib/datamappify/repository.rb +9 -0
  20. data/lib/datamappify/repository/query_method/callbacks.rb +33 -6
  21. data/lib/datamappify/repository/query_method/count.rb +3 -1
  22. data/lib/datamappify/repository/query_method/method.rb +8 -5
  23. data/lib/datamappify/repository/query_method/method/reference_handler.rb +27 -4
  24. data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +3 -1
  25. data/lib/datamappify/repository/query_method/save.rb +1 -6
  26. data/lib/datamappify/repository/query_method/update.rb +4 -0
  27. data/lib/datamappify/repository/query_methods.rb +42 -11
  28. data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +2 -2
  29. data/lib/datamappify/version.rb +1 -1
  30. data/spec/extensions/kaminari_spec.rb +27 -0
  31. data/spec/repository/associations/has_many_spec.rb +9 -42
  32. data/spec/repository/associations/has_one_spec.rb +185 -0
  33. data/spec/repository/callbacks_spec.rb +55 -14
  34. data/spec/repository/dirty_tracking_spec.rb +0 -1
  35. data/spec/repository/finders/criteria_spec.rb +1 -0
  36. data/spec/repository/persistence_spec.rb +21 -0
  37. data/spec/repository_spec.rb +18 -2
  38. data/spec/spec_helper.rb +0 -2
  39. data/spec/support/entities/group.rb +2 -1
  40. data/spec/support/repositories/active_record/group_repository.rb +2 -1
  41. data/spec/support/repositories/hero_user_repository.rb +12 -0
  42. data/spec/support/repositories/sequel/group_repository.rb +2 -1
  43. data/spec/support/shared/contexts.rb +1 -1
  44. data/spec/support/shared/examples/data/association_data_records.rb +28 -0
  45. data/spec/unit/entity/association/reference_spec.rb +4 -4
  46. data/spec/unit/entity/compatibility/active_record_spec.rb +19 -0
  47. metadata +46 -17
  48. data/lib/datamappify/data/criteria/relational/limit.rb +0 -13
  49. data/spec/support/monkey_patches/database_cleaner.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42a1f0451b17ea3e731eab36334ca301d2f82c54
4
- data.tar.gz: fe1e60e36f3c5885829e0b3e109e1c6ae799572d
3
+ metadata.gz: b8b5c9791648cd0a93b4a98014a63d37830b40bd
4
+ data.tar.gz: 24be82a4d65be38dd1097fdeed79d1176e25b66a
5
5
  SHA512:
6
- metadata.gz: 32641a2d06c6f2eb8281422edee1e292bb414c932266fcf04b0fd4fa7b59ddb75ae370c4d30295fd08d4b84f5f555c8208efe0a43bdfe1847d3b7051976f9b81
7
- data.tar.gz: 12f10eda25ac22872ca671ef411531709448d547822bad740ca822ac306c2958cebf788cd6749c64ec28300c7919356abddf70a6d86b424b518646c0c649c1c1
6
+ metadata.gz: f3dea093938a3ad30b10fb5b84456d7716c6376e2a4a653f472783b6268311bdd9e47cf8b227263b76eded63e48b94ad48cea35e9ca5aeb118a8c17d6f5af4f4
7
+ data.tar.gz: 631134bfc10968a87b3c9c219c56107a3c030e3481d12bf8c736ed98d9a87a182024386fbc43e83b0e5dc03151b25fc7bf6abd36027ab58ed25df97e2ce0fd7f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## master
2
2
 
3
+ ## 0.70.0.beta2 [2013-10-22]
4
+
5
+ - Proper dirty tracking in record updating, fixes an issue of setting an attribute to `nil`.
6
+ - Added `has_one` association.
7
+ - Added `before_load`, `after_load`, `before_find` and `after_find` callbacks.
8
+ - Added `before_init` and `after_init` callbacks.
9
+ - Added `offset` argument to the `limit` criteria.
10
+ - Added support for Kaminari pagination.
11
+
3
12
  ## 0.70.0.beta1 [2013-09-26]
4
13
 
5
14
  - Fixed dirty tracking for entities returned from `where` and `all`.
data/README.md CHANGED
@@ -319,7 +319,7 @@ users = UserRepository.criteria(
319
319
  :order => {
320
320
  :last_name => :asc
321
321
  },
322
- :limit => 10
322
+ :limit => [10, 20]
323
323
  )
324
324
  ```
325
325
 
@@ -328,7 +328,7 @@ Currently implemented criteria options:
328
328
  - where(Hash)
329
329
  - match(Hash)
330
330
  - order(Hash)
331
- - limit(Integer)
331
+ - limit(Array<limit(Integer), offset(Integer)>)
332
332
 
333
333
  _Note: it does not currently support searching attributes from different data providers._
334
334
 
@@ -380,14 +380,30 @@ Note that due to the attributes mapping, any data found in mapped records are no
380
380
  UserRepository.destroy(user)
381
381
  ```
382
382
 
383
+ #### Initialising an entity
384
+
385
+ Accepts an entity class and returns a new entity.
386
+
387
+ This is useful for using `before_init` and `after_init` callbacks to set up the entity.
388
+
389
+ ```ruby
390
+ UserRepository.init(user_class) #=> user
391
+ ```
392
+
383
393
  #### Callbacks
384
394
 
385
395
  Datamappify supports the following callbacks via [Hooks](https://github.com/apotonick/hooks):
386
396
 
397
+ - before_init
398
+ - before_load
399
+ - before_find
387
400
  - before_create
388
401
  - before_update
389
402
  - before_save
390
403
  - before_destroy
404
+ - after_init
405
+ - after_load
406
+ - after_find
391
407
  - after_create
392
408
  - after_update
393
409
  - after_save
@@ -427,7 +443,8 @@ Note: Returning either `nil` or `false` from the callback will cancel all subseq
427
443
 
428
444
  Datamappify also supports entity association. It is experimental and it currently supports the following association types:
429
445
 
430
- - belongs_to
446
+ - belongs_to (partially implemented)
447
+ - has_one
431
448
  - has_many
432
449
 
433
450
  Set up your entities and repositories:
@@ -438,9 +455,16 @@ Set up your entities and repositories:
438
455
  class User
439
456
  include Datamappify::Entity
440
457
 
458
+ has_one :title, :via => Title
441
459
  has_many :posts, :via => Post
442
460
  end
443
461
 
462
+ class Title
463
+ include Datamappify::Entity
464
+
465
+ belongs_to :user
466
+ end
467
+
444
468
  class Post
445
469
  include Datamappify::Entity
446
470
 
@@ -454,9 +478,16 @@ class UserRepository
454
478
 
455
479
  for_entity User
456
480
 
481
+ references :title, :via => TitleRepository
457
482
  references :posts, :via => PostRepository
458
483
  end
459
484
 
485
+ class TitleRepository
486
+ include Datamappify::Repository
487
+
488
+ for_entity Title
489
+ end
490
+
460
491
  class PostRepository
461
492
  include Datamappify::Repository
462
493
 
@@ -470,10 +501,12 @@ Usage examples:
470
501
  new_post = Post.new(post_attributes)
471
502
  another_new_post = Post.new(post_attributes)
472
503
  user = UserRepository.find(1)
504
+ user.title = Title.new(title_attributes)
473
505
  user.posts = [new_post, another_new_post]
474
506
 
475
507
  persisted_user = UserRepository.save!(user)
476
508
 
509
+ persisted_user.title #=> associated title
477
510
  persisted_user.posts #=> an array of associated posts
478
511
  ```
479
512
 
@@ -503,6 +536,25 @@ Datamappify.config do |c|
503
536
  end
504
537
  ```
505
538
 
539
+ ### Built-in extensions
540
+
541
+ Datamappify ships with a few extensions to make certain tasks easier.
542
+
543
+ #### Kaminari
544
+
545
+ Use `Criteria` with `page` and `per`.
546
+
547
+ ```ruby
548
+ UserRepository.criteria(
549
+ :where => {
550
+ :gender => 'male',
551
+ :age => 42
552
+ },
553
+ :page => 1,
554
+ :per => 10
555
+ )
556
+ ```
557
+
506
558
  ## API Documentation
507
559
 
508
560
  - [Rubygem release version](http://rubydoc.info/gems/datamappify/frames)
data/datamappify.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "virtus", ">= 1.0.0.beta6", "< 2.0"
21
+ spec.add_dependency "virtus", "~> 1.0.0"
22
22
  spec.add_dependency "activemodel", "~> 4.0.0"
23
23
  spec.add_dependency "hooks", "~> 0.3.0"
24
24
 
@@ -28,11 +28,13 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "yard"
29
29
  spec.add_development_dependency "rspec"
30
30
  spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "pry-stack_explorer"
31
32
  spec.add_development_dependency "simplecov"
32
33
  spec.add_development_dependency "cane"
33
34
  spec.add_development_dependency "coveralls"
34
35
  spec.add_development_dependency "sqlite3"
35
36
  spec.add_development_dependency "sequel"
36
37
  spec.add_development_dependency "activerecord", "~> 4.0.0"
37
- spec.add_development_dependency "database_cleaner", "~> 1.0.1"
38
+ spec.add_development_dependency "kaminari"
39
+ spec.add_development_dependency "database_cleaner", "~> 1.2.0"
38
40
  end
@@ -2,7 +2,13 @@ module Datamappify
2
2
  module Data
3
3
  module Criteria
4
4
  module ActiveRecord
5
- class Limit < Relational::Limit
5
+ class Limit < Common
6
+ def records(scope = nil)
7
+ limit, offset = criteria
8
+ offset ||= 0
9
+
10
+ (scope || source_class).limit(limit).offset(offset)
11
+ end
6
12
  end
7
13
  end
8
14
  end
@@ -15,7 +15,13 @@ module Datamappify
15
15
  end
16
16
 
17
17
  def save(record)
18
- record.update_attributes attributes_and_values
18
+ sanitise_attributes!(record)
19
+
20
+ record.update_attributes(attributes_and_values) unless ignore?
21
+ end
22
+
23
+ def sanitise_attributes!(record)
24
+ attributes_and_values.delete_if { |_, v| v.nil? } if record.new_record?
19
25
  end
20
26
  end
21
27
  end
@@ -138,13 +138,13 @@ module Datamappify
138
138
 
139
139
  private
140
140
 
141
- # Ignores the attribute if it isn't dirty or if it's a primary key
142
- #
143
- # @todo implement proper dirty attribute tracking
141
+ # Ignores the attribute accordingly
144
142
  #
145
143
  # @return [Boolean]
146
144
  def ignore_attribute?(attribute)
147
- entity.send(attribute.name).nil? || attribute.primary_key?
145
+ unless new_record? || entity.lazy_loaded?
146
+ !options[:dirty_attributes].include?(attribute.key)
147
+ end
148
148
  end
149
149
  end
150
150
  end
@@ -1,10 +1,12 @@
1
+ require 'datamappify/data/criteria/relational/criteria'
2
+
1
3
  module Datamappify
2
4
  module Data
3
5
  module Criteria
4
6
  module Relational
5
- class Count < Common
7
+ class Count < Criteria
6
8
  def perform
7
- source_class.count
9
+ (records || source_class).count
8
10
  end
9
11
  end
10
12
  end
@@ -2,7 +2,10 @@ module Datamappify
2
2
  module Data
3
3
  module Criteria
4
4
  module Sequel
5
- class Limit < Relational::Limit
5
+ class Limit < Common
6
+ def records(scope = nil)
7
+ (scope || source_class).limit(*criteria)
8
+ end
6
9
  end
7
10
  end
8
11
  end
@@ -15,7 +15,13 @@ module Datamappify
15
15
  end
16
16
 
17
17
  def save(record)
18
- record.update attributes_and_values
18
+ sanitise_attributes!(record)
19
+
20
+ record.update(attributes_and_values) unless ignore?
21
+ end
22
+
23
+ def sanitise_attributes!(record)
24
+ attributes_and_values.delete_if { |_, v| v.nil? } if record.new?
19
25
  end
20
26
  end
21
27
  end
@@ -28,6 +28,17 @@ module Datamappify
28
28
  references name
29
29
  end
30
30
 
31
+ # @param name [Symbol, String]
32
+ #
33
+ # @param options [Hash]
34
+ #
35
+ # @return [void]
36
+ def has_one(name, options = {})
37
+ attribute name, options[:via]
38
+
39
+ self.associations << name
40
+ end
41
+
31
42
  # @param name [Symbol, String]
32
43
  #
33
44
  # @param options [Hash]
@@ -32,7 +32,7 @@ module Datamappify
32
32
  # @return [void]
33
33
  def aggregate_validation_errors(context)
34
34
  self.associations.each do |association|
35
- self.send(association).reject(&:destroy?).each do |entity|
35
+ Array.wrap(self.send(association)).reject(&:destroy?).each do |entity|
36
36
  entity.invalid?(context) && referenced_entity_validation_errors(entity)
37
37
  end
38
38
  end
@@ -8,6 +8,8 @@ module Datamappify
8
8
 
9
9
  def self.included(klass)
10
10
  klass.class_eval do
11
+ extend ClassMethods
12
+
11
13
  attribute :_destroy, Virtus::Attribute::Boolean, :default => false
12
14
  end
13
15
  end
@@ -16,6 +18,17 @@ module Datamappify
16
18
  def destroy?
17
19
  !!_destroy
18
20
  end
21
+
22
+ module ClassMethods
23
+ # a minimal compatibility implementation needed by nested_form
24
+ #
25
+ # @see: https://github.com/ryanb/nested_form/blob/68485d/lib/nested_form/builder_mixin.rb#L28
26
+ def reflect_on_association(association)
27
+ if member_type = self.attribute_set.detect { |a| a.name == association }.try(:member_type)
28
+ Struct.new(:klass).new(member_type.primitive)
29
+ end
30
+ end
31
+ end
19
32
  end
20
33
  end
21
34
  end
@@ -3,10 +3,26 @@ module Datamappify
3
3
  module Compatibility
4
4
  module Association
5
5
  # Compatibility layer for ActionRecord
6
+ #
7
+ # adds `association_name_attributes` as required by nested attributes assignment
6
8
  module ActiveRecord
7
- # Adds `association_name_attributes` as required by
8
- # nested attributes assignment
9
- #
9
+ # @param (see DSL#has_one)
10
+ def has_one(name, options = {})
11
+ super
12
+
13
+ self.class_eval <<-CODE, __FILE__, __LINE__ + 1
14
+ def #{name}_attributes=(params = {})
15
+ params.delete('id') if params['id'].blank?
16
+
17
+ if self.#{name}
18
+ params['id'] = self.#{name}.id
19
+ end
20
+
21
+ self.#{name} = #{options[:via]}.new(params)
22
+ end
23
+ CODE
24
+ end
25
+
10
26
  # @param (see DSL#has_many)
11
27
  def has_many(name, options = {})
12
28
  super
@@ -37,7 +37,7 @@ module Datamappify
37
37
  attribute.name
38
38
  end
39
39
 
40
- Virtus::Attribute.build(attribute.type, attribute.options.merge(:name => attribute_name))
40
+ attribute.rename(attribute_name)
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,16 @@
1
+ module Datamappify
2
+ module Extensions
3
+ module Kaminari
4
+ module CollectionMethods
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ include ::Kaminari::ConfigurationMethods::ClassMethods
8
+ include ::Kaminari::PageScopeMethods
9
+
10
+ attr_accessor :limit_value, :offset_value, :total_count
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ require 'datamappify/extensions/kaminari/collection_methods'
2
+
3
+ module Datamappify
4
+ module Extensions
5
+ module Kaminari
6
+ module CriteriaProcessor
7
+ # @see (Repository::QueryMethods#criteria)
8
+ def criteria(criteria)
9
+ process_criteria!(criteria)
10
+
11
+ collection = super
12
+
13
+ collection.class_eval { include CollectionMethods }
14
+
15
+ collection.limit_value = @limit
16
+ collection.offset_value = @offset
17
+ collection.total_count = count(criteria.except(:limit))
18
+
19
+ collection
20
+ end
21
+
22
+ private
23
+
24
+ # @see (Repository::QueryMethods#criteria)
25
+ def process_criteria!(criteria)
26
+ if (page = criteria.delete(:page).try(:to_i)) && (per = criteria.delete(:per).try(:to_i))
27
+ @limit = per
28
+ @offset = ([page, 1].max - 1) * per
29
+
30
+ criteria[:limit] = [@limit, @offset]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -111,9 +111,9 @@ module Datamappify
111
111
  :attributes => attributes,
112
112
  :dirty_aware? => true,
113
113
  :dirty_attributes => []
114
- }).execute do |provider_name, source_class, attributes|
114
+ }).execute do |provider_name, source_class, options|
115
115
  entity.repository.data_mapper.provider(provider_name).build_criteria(
116
- :FindByKey, source_class, entity, attributes
116
+ :FindByKey, source_class, entity, options[:attributes]
117
117
  )
118
118
  end
119
119
  end
@@ -1,3 +1,8 @@
1
+ begin
2
+ require 'kaminari'
3
+ rescue LoadError
4
+ end
5
+
1
6
  require 'datamappify/repository/lazy_checking'
2
7
  require 'datamappify/repository/mapping_dsl'
3
8
  require 'datamappify/repository/unit_of_work'
@@ -51,6 +56,10 @@ module Datamappify
51
56
  def_delegators :instance, *QueryMethods.instance_methods
52
57
  end
53
58
  end
59
+
60
+ def method_missing(symbol, *args, &block)
61
+ instance.send(symbol, *args, &block)
62
+ end
54
63
  end
55
64
  end
56
65
  end