ninja-model 0.8.1 → 0.9.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 (72) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile +1 -0
  3. data/Rakefile +6 -0
  4. data/lib/ninja_model.rb +23 -7
  5. data/lib/ninja_model/adapters.rb +25 -20
  6. data/lib/ninja_model/adapters/adapter_manager.rb +2 -0
  7. data/lib/ninja_model/adapters/adapter_pool.rb +2 -0
  8. data/lib/ninja_model/associations.rb +63 -101
  9. data/lib/ninja_model/associations/association.rb +146 -0
  10. data/lib/ninja_model/associations/association_scope.rb +39 -0
  11. data/lib/ninja_model/associations/belongs_to_association.rb +33 -21
  12. data/lib/ninja_model/associations/builder/association.rb +57 -0
  13. data/lib/ninja_model/associations/builder/belongs_to.rb +33 -0
  14. data/lib/ninja_model/associations/builder/collection_association.rb +60 -0
  15. data/lib/ninja_model/associations/builder/has_many.rb +11 -0
  16. data/lib/ninja_model/associations/builder/has_one.rb +20 -0
  17. data/lib/ninja_model/associations/builder/singular_association.rb +49 -0
  18. data/lib/ninja_model/associations/collection_association.rb +103 -0
  19. data/lib/ninja_model/associations/collection_proxy.rb +45 -0
  20. data/lib/ninja_model/associations/has_many_association.rb +19 -43
  21. data/lib/ninja_model/associations/has_one_association.rb +52 -6
  22. data/lib/ninja_model/associations/singular_association.rb +61 -0
  23. data/lib/ninja_model/attribute.rb +5 -2
  24. data/lib/ninja_model/attribute_methods.rb +35 -40
  25. data/lib/ninja_model/base.rb +31 -39
  26. data/lib/ninja_model/identity.rb +8 -6
  27. data/lib/ninja_model/rails_ext/active_record.rb +69 -225
  28. data/lib/ninja_model/railtie.rb +0 -9
  29. data/lib/ninja_model/reflection.rb +103 -20
  30. data/lib/ninja_model/relation.rb +2 -2
  31. data/lib/ninja_model/relation/query_methods.rb +16 -0
  32. data/lib/ninja_model/version.rb +1 -1
  33. data/ninja-model.gemspec +2 -1
  34. data/spec/db/schema.rb +15 -0
  35. data/spec/{ninja_model → lib/ninja_model}/adapters/abstract_adapter_spec.rb +0 -0
  36. data/spec/lib/ninja_model/adapters/adapter_manager_spec.rb +72 -0
  37. data/spec/lib/ninja_model/adapters/adapter_pool_spec.rb +230 -0
  38. data/spec/lib/ninja_model/adapters_spec.rb +120 -0
  39. data/spec/lib/ninja_model/associations/belongs_to_association_spec.rb +76 -0
  40. data/spec/lib/ninja_model/associations/has_many_association_spec.rb +118 -0
  41. data/spec/lib/ninja_model/associations/has_one_association_spec.rb +80 -0
  42. data/spec/{ninja_model → lib/ninja_model}/attribute_methods_spec.rb +2 -2
  43. data/spec/{ninja_model → lib/ninja_model}/attribute_spec.rb +4 -0
  44. data/spec/{ninja_model → lib/ninja_model}/base_spec.rb +0 -0
  45. data/spec/{ninja_model → lib/ninja_model}/identity_spec.rb +0 -0
  46. data/spec/{ninja_model → lib/ninja_model}/persistence_spec.rb +0 -0
  47. data/spec/{ninja_model → lib/ninja_model}/predicate_spec.rb +0 -0
  48. data/spec/{ninja_model → lib/ninja_model}/query_methods_spec.rb +0 -0
  49. data/spec/{ninja_model → lib/ninja_model}/reflection_spec.rb +7 -9
  50. data/spec/{ninja_model → lib/ninja_model}/relation_spec.rb +0 -0
  51. data/spec/{ninja_model → lib/ninja_model}/symbol_spec.rb +0 -0
  52. data/spec/{ninja_model → lib/ninja_model}/validation_spec.rb +0 -0
  53. data/spec/{ninja_model_spec.rb → lib/ninja_model_spec.rb} +0 -0
  54. data/spec/models/bio.rb +8 -0
  55. data/spec/models/body.rb +7 -0
  56. data/spec/models/category.rb +7 -0
  57. data/spec/models/email_address.rb +3 -0
  58. data/spec/models/post.rb +13 -0
  59. data/spec/models/tag.rb +3 -0
  60. data/spec/models/user.rb +4 -0
  61. data/spec/spec_helper.rb +38 -5
  62. data/spec/support/dummy_adapter/adapter.rb +48 -0
  63. data/spec/support/factories/bio.rb +11 -0
  64. data/spec/support/factories/body.rb +12 -0
  65. data/spec/support/factories/email_address.rb +5 -0
  66. data/spec/support/factories/post.rb +12 -0
  67. data/spec/support/factories/tag.rb +5 -0
  68. data/spec/support/factories/user.rb +5 -0
  69. metadata +121 -63
  70. data/spec/ninja_model/adapters/adapter_manager_spec.rb +0 -69
  71. data/spec/ninja_model/adapters/adapter_pool_spec.rb +0 -230
  72. data/spec/ninja_model/adapters_spec.rb +0 -85
@@ -0,0 +1,39 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class AssociationScope
4
+
5
+ attr_reader :association
6
+
7
+ delegate :klass, :owner, :reflection, :to => :association
8
+ delegate :chain, :conditions, :options, :ninja_model, :active_record, :to => :reflection
9
+
10
+ def initialize(association)
11
+ @association = association
12
+ end
13
+
14
+ def scope
15
+ scope = klass.unscoped
16
+ scope = scope.extending(*Array.wrap(options[:extend]))
17
+ scope = scope.apply_finder_options(
18
+ options.slice(
19
+ :order, :limit, :joins, :group, :having, :offset
20
+ )
21
+ )
22
+
23
+ add_constraints(scope)
24
+ end
25
+
26
+ def add_constraints(scope)
27
+ if reflection.source_macro == :belongs_to
28
+ key = reflection.association_primary_key
29
+ foreign_key = reflection.foreign_key
30
+ else
31
+ key = reflection.foreign_key
32
+ foreign_key = reflection.active_record_primary_key
33
+ end
34
+
35
+ scope = scope.where(key => owner[foreign_key])
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,38 +1,50 @@
1
1
  module NinjaModel
2
2
  module Associations
3
- class BelongsToAssociation < AssociationProxy
4
- def initialize(owner, reflection)
5
- @owner, @reflection = owner, reflection
6
- end
3
+ class BelongsToAssociation < SingularAssociation
7
4
 
8
5
  def replace(record)
9
- if record.nil?
10
- @target = @owner[@reflection.primary_key_name] = nil
11
- else
12
- @target = (AssociationProxy === record ? record.target : record)
13
- @owner[@reflection.primary_key_name] = record_id(record) if record.persisted?
14
- @updated = true
15
- end
16
- loaded
17
- record
6
+ raise_on_type_mismatch(record) if record
7
+
8
+ replace_keys(record)
9
+
10
+ @updated = true if record
11
+
12
+ self.target = record
13
+ end
14
+
15
+ def updated?
16
+ @updated
18
17
  end
19
18
 
20
19
  private
21
20
 
22
- def find_target
23
- if @reflection.options[:primary_key]
24
- @reflection.klass.scoped.where( @reflection.options[:primary_key] => @owner.send(@reflection.primary_key_name)).first
21
+ def find_target?
22
+ !loaded? && foreign_key_present? && klass
23
+ end
24
+
25
+ def different_target?(record)
26
+ record.nil? && owner[reflection.foreign_key] ||
27
+ record && record.id != owner[reflection.foreign_key]
28
+ end
29
+
30
+ def replace_keys(record)
31
+ if record
32
+ owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
25
33
  else
26
- @reflection.klass.scoped.where( :id => @owner.send(@reflection.primary_key_name)).first
34
+ owner[reflection.foreign_key] = nil
27
35
  end
28
36
  end
29
37
 
30
- def record_id(record)
31
- record.send(@reflection.options[:primary_key] || :id)
38
+ def foreign_key_present?
39
+ owner[reflection.foreign_key]
32
40
  end
33
41
 
34
- def foreign_key_present
35
- !@owner[@reflection.primary_key_name].nil?
42
+ def target_id
43
+ if options[:primary_key]
44
+ owner.send(reflection.name).try(:id)
45
+ else
46
+ owner[reflection.foreign_key]
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -0,0 +1,57 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class Association
5
+ class_attribute :valid_options
6
+
7
+ self.valid_options = [:class_name, :foreign_key, :select, :conditions, :extend]
8
+
9
+ class_attribute :macro
10
+
11
+ attr_reader :model, :name, :options, :reflection
12
+
13
+ def self.build(model, name, options)
14
+ new(model, name, options).build
15
+ end
16
+
17
+ def initialize(model, name, options)
18
+ @model, @name, @options = model, name, options
19
+ end
20
+
21
+ def build
22
+ validate_options
23
+ reflection = model.create_reflection(self.class.macro, name, options, model)
24
+ define_accessors
25
+ reflection
26
+ end
27
+
28
+ private
29
+
30
+ def validate_options
31
+ options.assert_valid_keys(self.class.valid_options)
32
+ end
33
+
34
+ def define_accessors
35
+ define_readers
36
+ define_writers
37
+ end
38
+
39
+ def define_readers
40
+ name = self.name
41
+
42
+ model.redefine_method(name) do |*params|
43
+ association(name).reader(*params)
44
+ end
45
+ end
46
+
47
+ def define_writers
48
+ name = self.name
49
+
50
+ model.redefine_method("#{name}=") do |value|
51
+ association(name).writer(value)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class BelongsTo < SingularAssociation
5
+ self.macro = :belongs_to
6
+
7
+ def build
8
+ reflection = super
9
+ configure_dependency
10
+ reflection
11
+ end
12
+
13
+ private
14
+
15
+ def configure_dependency
16
+ if options[:dependent]
17
+ unless options[:dependenc].in?([:destroy, :delete])
18
+ raise ArgumentError, "The :dependent option expects either :destroy or delete (#{options[:dependent].inspect})"
19
+ end
20
+
21
+ method_name = "belongs_to_dependent_#{options[:dependent]}_for_#{name}"
22
+ model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
23
+ def #{method_name}
24
+ association = #{name}
25
+ association.#{options[:dependent]} if association
26
+ end
27
+ eoruby
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class CollectionAssociation < Association
5
+ CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
6
+
7
+ self.valid_options += [
8
+ :order, :group, :limit, :offset, :before_add, :after_add,
9
+ :before_remove, :after_remove
10
+ ]
11
+
12
+ def self.build(model, name, options, &extension)
13
+ new(model, name, options, &extension).build
14
+ end
15
+
16
+ def initialize(model, name, options, &extension)
17
+ super(model, name, options)
18
+ end
19
+
20
+ def build
21
+ reflection = super
22
+ CALLBACKS.each { |callback_name| define_callback(callback_name) }
23
+ reflection
24
+ end
25
+
26
+ def writable?
27
+ true
28
+ end
29
+
30
+ private
31
+
32
+ def define_callback(callback_name)
33
+ full_callback_name = "#{callback_name}_for_#{name}"
34
+
35
+ model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
36
+
37
+ model.send("#{full_callback_name}=", Array.wrap(options[callback_name.to_sym]))
38
+ end
39
+
40
+ def define_readers
41
+ super
42
+
43
+ name = self.name
44
+ model.redefine_method("#{name.to_s.singularize}_ids") do
45
+ association(name).ids_reader
46
+ end
47
+ end
48
+
49
+ def define_writers
50
+ super
51
+
52
+ name = self.name
53
+ model.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
54
+ association(name).ids_writer(ids)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,11 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class HasMany < CollectionAssociation
5
+ self.macro = :has_many
6
+
7
+ self.valid_options += [:primary_key]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class HasOne < SingularAssociation
5
+ self.macro = :has_one
6
+
7
+ def constructable?
8
+ true
9
+ end
10
+
11
+ private
12
+
13
+ def validate_options
14
+ valid_options = self.class.valid_options
15
+ options.assert_valid_keys(valid_options)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ module NinjaModel
2
+ module Associations
3
+ module Builder
4
+ class SingularAssociation < Association
5
+ self.valid_options += [:dependent, :primary_key]
6
+
7
+ def constructable?
8
+ true
9
+ end
10
+
11
+ def define_accessors
12
+ super
13
+ define_constructors if constructable?
14
+ end
15
+
16
+ private
17
+
18
+ def define_readers
19
+ super
20
+ name = self.name
21
+
22
+ model.redefine_method("#{name}_loaded?") do
23
+ ActiveSupport::Deprecation.warn(
24
+ "Calling obj.#{name}_loaded? is deprecated. Please use " \
25
+ "obj.association(:#{name}).loaded? instead."
26
+ )
27
+ association(name).loaded?
28
+ end
29
+ end
30
+
31
+ def define_constructors
32
+ name = self.name
33
+
34
+ model.redefine_method("build_#{name}") do |*params, &block|
35
+ association(name).build(*params, &block)
36
+ end
37
+
38
+ model.redefine_method("create_#{name}") do |*params, &block|
39
+ association(name).create(*params, &block)
40
+ end
41
+
42
+ model.redefine_method("create_#{name}!") do |*params, &block|
43
+ association(name).create!(*params, &block)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,103 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class CollectionAssociation < Association
4
+ attr_reader :proxy
5
+ delegate :select, :find, :first, :last, :count, :size, :length,
6
+ :empty?, :any?, :to => :scoped
7
+
8
+ def initialize(owner, reflection)
9
+ super
10
+ @proxy = CollectionProxy.new(self)
11
+ end
12
+
13
+ def reader(force_reload = false)
14
+ if force_reload
15
+ klass.uncached { reload }
16
+ end
17
+ proxy
18
+ end
19
+
20
+ def reset
21
+ @loaded = false
22
+ @target = []
23
+ end
24
+
25
+ #def find(*args)
26
+ # if block_given?
27
+ # load_target.find(*args) { |*block_args| yield(*block_args) }
28
+ # else
29
+ # scoped.find(*args)
30
+ # end
31
+ #end
32
+
33
+ def first(*args)
34
+ first_or_last(:first, *args)
35
+ end
36
+
37
+ def last(*args)
38
+ first_or_last(:last, *args)
39
+ end
40
+
41
+ def build(attributes = {}, options = {}, &block)
42
+ if attributes.is_a?(Array)
43
+ attributes.collect { |attr| build(attr, options, &block) }
44
+ else
45
+ add_to_target(build_record(attributes, options)) do |record|
46
+ yield(record) if block_given?
47
+ end
48
+ end
49
+ end
50
+
51
+ #def create(attributes = {}, options = {}, &block)
52
+ # create_record(attributes, options, &block)
53
+ #end
54
+
55
+ #def create!(attributes = {}, options = {}, &block)
56
+ # create_record(attributes, options, true, &block)
57
+ #end
58
+
59
+ def add_to_target(record)
60
+ yield(record) if block_given?
61
+ @target << record
62
+ record
63
+ end
64
+
65
+ private
66
+
67
+ def find_target
68
+ puts "find_target for #{self}"
69
+ scoped.all
70
+ end
71
+
72
+ #def create_record(attributes, options, raise = false, &block)
73
+ # unless owner.persisted?
74
+ # raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
75
+ # end
76
+
77
+ # if attributes.is_a?(Array)
78
+ # attributes.collect { |attr| create_record(attr, options, raise, &block) }
79
+ # else
80
+ # add_to_target(build_record(attributes, options)) do |record|
81
+ # yield(record) if block_given?
82
+ # insert_record(record, true, raise)
83
+ # end
84
+ # end
85
+ #end
86
+
87
+ #def insert_record(record, validate = true, raise = false)
88
+ # raise NotImplementedError
89
+ #end
90
+
91
+ def create_scope
92
+ scoped.scope_for_create.stringify_keys
93
+ end
94
+
95
+ def first_or_last(type, *args)
96
+ args.shift if args.first.is_a?(Hash) && args.first.empty?
97
+
98
+ collection = scoped.all
99
+ collection.send(type, *args)
100
+ end
101
+ end
102
+ end
103
+ end