ninja-model 0.8.1 → 0.9.0

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