datamappify 0.70.0.beta1 → 0.70.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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