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
data/.gitignore CHANGED
@@ -5,7 +5,7 @@
5
5
  tmp/**/*
6
6
  doc/*
7
7
  .DS_Store
8
- db/*.sqlite3
8
+ *.sqlite3
9
9
  doc/api
10
10
  doc/app
11
11
  doc/plugins
data/Gemfile CHANGED
@@ -4,4 +4,5 @@ gemspec
4
4
  group :test, :development do
5
5
  gem 'yard'
6
6
  gem 'simplecov', :require => false
7
+ gem 'factory_girl'
7
8
  end
data/Rakefile CHANGED
@@ -8,6 +8,12 @@ end
8
8
  require 'rspec/core/rake_task'
9
9
  require 'yard'
10
10
 
11
+ #task :prepare_db do
12
+ # db_root = File.join(File.expand_path('../', __FILE__), 'spec', 'db')
13
+ # db_file = File.join(db_root, 'test.sqlite3')
14
+ # sh "rm #{db_file}" if File.exists?(db_file)
15
+ # sh %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
16
+ #end
11
17
 
12
18
  RSpec::Core::RakeTask.new(:spec) do |t|
13
19
  t.verbose = false
data/lib/ninja_model.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'active_model'
2
+ require 'active_record'
2
3
  require 'active_support/core_ext'
3
4
 
4
5
  module NinjaModel
6
+ extend ActiveSupport::Autoload
7
+
5
8
  class NinjaModelError < StandardError; end
6
9
 
7
10
  class << self
@@ -12,10 +15,10 @@ module NinjaModel
12
15
  end
13
16
 
14
17
  def ninja_model?(symbol)
15
- klass = symbol.to_s.camelize
16
- klass = klass.singularize
17
- klass = klass.constantize
18
- klass.ancestors.include?(NinjaModel::Base)
18
+ #klass = symbol.to_s.camelize
19
+ #klass = klass.singularize
20
+ #klass = symbol.constantize
21
+ symbol.ancestors.include?(NinjaModel::Base)
19
22
  end
20
23
 
21
24
  def configuration
@@ -23,12 +26,25 @@ module NinjaModel
23
26
  end
24
27
  end
25
28
 
26
- class Base
29
+ autoload :Attribute
30
+ autoload :AttributeMethods
31
+ autoload :Associations
32
+ autoload :Adapters
33
+ autoload :Base
34
+ autoload :Callbacks
35
+ autoload :Identity
36
+ autoload :Persistence
37
+ autoload :Predicate
38
+ autoload :Reflection
39
+ autoload :Relation
40
+ autoload :Validation
41
+
42
+ ActiveSupport.on_load(:active_record) do
43
+ require 'ninja_model/rails_ext/active_record'
44
+ include ActiveRecord::NinjaModelExtensions::ReflectionExt
27
45
  end
28
46
  end
29
47
 
30
- require 'ninja_model/base'
31
- require 'ninja_model/core_ext/symbol'
32
48
  if defined?(Rails)
33
49
  require 'ninja_model/railtie'
34
50
  end
@@ -1,47 +1,52 @@
1
- require 'ninja_model/adapters/abstract_adapter'
2
- require 'ninja_model/adapters/adapter_manager'
3
- require 'ninja_model/adapters/adapter_pool'
4
- require 'ninja_model/adapters/adapter_specification'
5
-
6
1
  module NinjaModel
2
+ class AdapterNotSpecified < StandardError; end
3
+ class InvalidAdapter < StandardError; end
4
+ class InvalidSpecification < StandardError; end
7
5
 
8
6
  module Adapters
9
- class AdapterNotSpecified < StandardError; end
10
- class InvalidAdapter < StandardError; end
11
- class InvalidSpecification < StandardError; end
12
- end
7
+ extend ActiveSupport::Concern
13
8
 
14
- class Base
15
- class_attribute :adapter_manager
16
- self.adapter_manager = NinjaModel::Adapters::AdapterManager.new
9
+ autoload :AbstractAdapter, 'ninja_model/adapters/abstract_adapter'
10
+ autoload :AdapterManager, 'ninja_model/adapters/adapter_manager'
11
+ autoload :AdapterPool, 'ninja_model/adapters/adapter_pool'
12
+ autoload :AdapterSpecification, 'ninja_model/adapters/adapter_specification'
13
+
14
+ included do
15
+ class_attribute :adapter_manager
16
+ self.adapter_manager = NinjaModel::Adapters::AdapterManager.new
17
+ end
17
18
 
18
19
  def adapter
19
20
  self.class.retrieve_adapter
20
21
  end
21
22
 
22
- class << self
23
+ module ClassMethods
23
24
  def register_adapter(name, klass)
24
- Adapters::AdapterManager.register_adapter_class(name, klass)
25
+ if klass.ancestors.include?(NinjaModel::Adapters::AbstractAdapter)
26
+ Adapters::AdapterManager.register_adapter_class(name, klass)
27
+ else
28
+ raise InvalidAdapter, "Invalid adapter: #{klass}"
29
+ end
25
30
  end
26
31
 
27
32
  def set_adapter(spec = nil)
28
33
  case spec
29
34
  when nil
30
- raise Adapters::AdapterNotSpecified unless defined?(Rails.env)
35
+ raise AdapterNotSpecified unless defined?(Rails.env)
31
36
  set_adapter(Rails.env)
32
- when Adapters::AdapterSpecification
33
- self.adapter_manager.create_adapter(name, spec)
34
37
  when Symbol, String
35
38
  if config = NinjaModel.configuration.specs[spec.to_s]
36
39
  set_adapter(config)
37
40
  else
38
- raise Adapters::InvalidSpecification, "#{spec} is not configured"
41
+ raise InvalidSpecification, "#{spec} is not configured"
39
42
  end
43
+ when Adapters::AdapterSpecification
44
+ self.adapter_manager.create_adapter(name, spec)
40
45
  else
41
46
  spec = spec.symbolize_keys
42
- raise Adapters::AdapterNotSpecified, "configuration does not specify adapter" unless spec.key?(:adapter)
47
+ raise AdapterNotSpecified, "configuration does not specify adapter: #{spec}" unless spec.key?(:adapter)
43
48
  adapter_name = spec[:adapter]
44
- raise Adapters::InvalidAdapter, "configuration does not specify adapter" unless Adapters::AdapterManager.registered?(adapter_name)
49
+ raise InvalidAdapter, "configuration does not specify adapter" unless Adapters::AdapterManager.registered?(adapter_name)
45
50
  shutdown_adapter
46
51
  set_adapter(Adapters::AdapterSpecification.new(spec, adapter_name))
47
52
  end
@@ -1,8 +1,10 @@
1
1
  module NinjaModel
2
2
  module Adapters
3
3
  class AdapterManager
4
+
4
5
  class_attribute :registered
5
6
  self.registered = {}
7
+
6
8
  class << self
7
9
  def registered?(name)
8
10
  registered.key?(name.to_sym)
@@ -1,5 +1,7 @@
1
1
  module NinjaModel
2
+
2
3
  class ConnectionTimeoutError < NinjaModelError; end
4
+
3
5
  module Adapters
4
6
  class AdapterPool
5
7
  attr_reader :spec, :instances
@@ -1,125 +1,87 @@
1
1
  module NinjaModel
2
+ module Associations
3
+ extend ActiveSupport::Concern
2
4
 
3
- class Base
4
- class << self
5
- def has_one(association_id, options = {})
6
- reflection = create_has_one_reflection(association_id, options)
7
- association_accessor_methods(reflection, Associations::HasOneAssociation)
8
- # TODO: Implement the build/create association methods
9
- #association_constructor_method(:build, reflection, HasOneAssociation)
10
- #association_constructor_method(:create, reflection, HasOneAssociation)
11
- #configure_dependency_for_has_one(reflection)
12
- end
13
-
14
- def belongs_to(association_id, options = {})
15
- reflection = create_belongs_to_reflection(association_id, options)
16
- association_accessor_methods(reflection, Associations::BelongsToAssociation)
17
- # TODO: Implement the build/create association methods
18
- #association_constructor_method(:build, reflection, BelongsToAssociation)
19
- #association_constructor_method(:create, reflection, BelongsToAssociation)
20
- end
21
-
22
- def has_many(association_id, options = {})
23
- reflection = create_has_many_reflection(association_id, options)
24
- collection_accessor_methods(reflection, Associations::HasManyAssociation)
25
- end
26
-
27
- private
5
+ autoload :Association, 'ninja_model/associations/association'
6
+ autoload :AssociationProxy, 'ninja_model/associations/association_proxy'
7
+ autoload :AssociationScope, 'ninja_model/associations/association_scope'
8
+ autoload :BelongsToAssociation, 'ninja_model/associations/belongs_to_association'
9
+ autoload :CollectionAssociation, 'ninja_model/associations/collection_association'
10
+ autoload :CollectionProxy, 'ninja_model/associations/collection_proxy'
11
+ autoload :HasOneAssociation, 'ninja_model/associations/has_one_association'
12
+ autoload :HasManyAssociation, 'ninja_model/associations/has_many_association'
13
+ autoload :SingularAssociation, 'ninja_model/associations/singular_association'
14
+
15
+ module Builder
16
+ autoload :Association, 'ninja_model/associations/builder/association'
17
+ autoload :SingularAssociation, 'ninja_model/associations/builder/singular_association'
18
+ autoload :CollectionAssociation, 'ninja_model/associations/builder/collection_association'
19
+ autoload :HasMany, 'ninja_model/associations/builder/has_many'
20
+ autoload :BelongsTo, 'ninja_model/associations/builder/belongs_to'
21
+ autoload :HasOne, 'ninja_model/associations/builder/has_one'
22
+ end
28
23
 
29
- def create_has_one_reflection(association, options = {})
30
- create_reflection(:has_one, association, options, self)
31
- end
24
+ attr_reader :association_cache
32
25
 
33
- def create_has_many_reflection(association, options = {})
34
- create_reflection(:has_many, association, options, self)
35
- end
26
+ def association(name)
27
+ association = association_instance_get(name)
36
28
 
37
- def create_belongs_to_reflection(association, options = {})
38
- create_reflection(:belongs_to, association, options, self)
29
+ if association.nil?
30
+ reflection = self.class.reflect_on_association(name)
31
+ association = reflection.association_class.new(self, reflection)
32
+ association_instance_set(name, association)
39
33
  end
34
+ association
35
+ end
40
36
 
41
- def association_accessor_methods(reflection, association_proxy_class)
42
- redefine_method(reflection.name) do |*params|
43
- association = association_instance_get(reflection.name)
44
-
45
- if association.nil?
46
- association = association_proxy_class.new(self, reflection)
47
- retval = association.reload
48
- if retval.nil? and association_proxy_class == Associations::BelongsToAssociation
49
- association_instance_set(reflection.name, nil)
50
- return nil
51
- end
52
- association_instance_set(reflection.name, association)
53
- end
54
- association.target.nil? ? nil : association
55
- end
37
+ private
56
38
 
57
- redefine_method("loaded_#{reflection.name}?") do
58
- association = association_instance_get(reflection.name)
59
- association && association.loaded?
60
- end
61
-
62
- redefine_method("#{reflection.name}=") do |new_value|
63
- association = association_instance_get(reflection.name)
64
- if association.nil? || association.target != new_value
65
- association = association_proxy_class.new(self, reflection)
66
- end
39
+ def association_instance_get(name)
40
+ @association_cache[name.to_sym]
41
+ end
67
42
 
68
- association.replace(new_value)
69
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
70
- end
43
+ def association_instance_set(name, association)
44
+ @association_cache[name.to_sym] = association
45
+ end
71
46
 
72
- redefine_method("set_#{reflection.name}_target") do |target|
73
- return if target.nil? and association_proxy_class == Associations::BelongsToAssociation
74
- association = association_proxy_class.new(self, reflection)
75
- association.target = target
76
- association_instance_set(reflection.name, association)
47
+ module ClassMethods
48
+ def add_autosave_association_callbacks(reflection)
49
+ end
50
+ def has_one(name, options = {})
51
+ klass = compute_klass(name, :has_one, options)
52
+ if NinjaModel.ninja_model?(klass)
53
+ Builder::HasOne.build(self, name, options)
54
+ else
55
+ ActiveRecord::Associations::Builder::HasOne.build(self, name, options)
77
56
  end
78
57
  end
79
58
 
80
- def collection_accessor_methods(reflection, association_proxy_class, writer = true)
81
- collection_reader_method(reflection, association_proxy_class)
82
-
83
- if writer
84
- redefine_method("#{reflection.name}=") do |new_value|
85
- association = send(reflection.name)
86
- association.replace(new_value)
87
- association
88
- end
59
+ def belongs_to(name, options = {}, &extension)
60
+ klass = compute_klass(name, :belongs_to, options)
61
+ if NinjaModel.ninja_model?(klass)
62
+ Builder::BelongsTo.build(self, name, options)
63
+ else
64
+ ActiveRecord::Associations::Builder::BelongsTo.build(self, name, options)
89
65
  end
90
66
  end
91
67
 
92
- def collection_reader_method(reflection, association_proxy_class)
93
- redefine_method(reflection.name) do |*params|
94
- association = association_instance_get(reflection.name)
95
-
96
- if association.nil?
97
- association = association_proxy_class.new(self, reflection)
98
- end
99
- association_instance_set(reflection.name, association)
100
- association
68
+ def has_many(name, options = {}, &extension)
69
+ klass = compute_klass(name, :has_many, options)
70
+ if NinjaModel.ninja_model?(klass)
71
+ Builder::HasMany.build(self, name, options)
72
+ else
73
+ ActiveRecord::Associations::Builder::HasMany.build(self, name, options)
101
74
  end
102
75
  end
103
- end
104
- end
105
-
106
- module Associations
107
76
 
108
- autoload :AssociationProxy, 'ninja_model/associations/association_proxy'
109
- autoload :HasOneAssociation, 'ninja_model/associations/has_one_association'
110
- autoload :HasManyAssociation, 'ninja_model/associations/has_many_association'
111
- autoload :BelongsToAssociation, 'ninja_model/associations/belongs_to_association'
77
+ private
112
78
 
113
- def association_instance_get(name)
114
- ivar = "@#{name}"
115
- if instance_variable_defined?(ivar)
116
- association = instance_variable_get(ivar)
117
- association if association.respond_to?(:loaded?)
79
+ def compute_klass(name, macro, options)
80
+ klass = options[:class_name] || name
81
+ klass = klass.to_s.camelize
82
+ klass = klass.singularize if macro.eql?(:has_many)
83
+ klass = compute_type(klass)
118
84
  end
119
85
  end
120
-
121
- def association_instance_set(name, association)
122
- instance_variable_set("@#{name}", association)
123
- end
124
86
  end
125
87
  end
@@ -0,0 +1,146 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class Association
4
+
5
+ attr_reader :owner, :target, :reflection
6
+
7
+ delegate :options, :to => :reflection
8
+
9
+ def initialize(owner, reflection)
10
+ @target = nil
11
+ @owner, @reflection = owner, reflection
12
+
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @loaded = false
18
+ @target = nil
19
+ end
20
+
21
+ def reload
22
+ reset
23
+ reset_scope
24
+ load_target
25
+ self unless target.nil?
26
+ end
27
+
28
+ def loaded?
29
+ @loaded
30
+ end
31
+
32
+ def loaded!
33
+ @loaded = true
34
+ end
35
+
36
+ def stale_target?
37
+ false
38
+ end
39
+
40
+ def target=(target)
41
+ @target = target
42
+ loaded!
43
+ end
44
+
45
+ def scoped
46
+ target_scope.merge(association_scope)
47
+ end
48
+
49
+ def target_scope
50
+ klass.scoped
51
+ end
52
+
53
+ def association_scope
54
+ if klass
55
+ @association_scope ||= AssociationScope.new(self).scope
56
+ end
57
+ end
58
+
59
+ def reset_scope
60
+ @association_scope = nil
61
+ end
62
+
63
+ def set_inverse_instance(record)
64
+ if record && invertible_for?(record)
65
+ inverse = record.association(inverse_reflection_for(record).name)
66
+ inverse.target = owner
67
+ end
68
+ end
69
+
70
+ def klass
71
+ reflection.klass
72
+ end
73
+
74
+ def target_scope
75
+ klass.scoped
76
+ end
77
+
78
+ def load_target
79
+ if find_target?
80
+ @target ||= find_target
81
+ end
82
+ loaded! unless loaded?
83
+ target
84
+ rescue NinjaModel::RecordNotFound
85
+ reset
86
+ end
87
+
88
+ private
89
+
90
+ def find_target?
91
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
92
+ end
93
+
94
+ def build_record(attributes, options)
95
+ record = reflection.build_association(attributes, options) do |r|
96
+ attrs = create_scope.except(*r.changed)
97
+ r.assign_attributes(
98
+ create_scope.except(*r.changed)
99
+ )
100
+ end
101
+ end
102
+
103
+ def creation_attributes
104
+ attributes = {}
105
+ if options[:through]
106
+ raise NotImplementedError, "NinjaModel does not support through associations yet."
107
+ else
108
+ if reflection.macro.in?([:has_one, :has_many])
109
+ attributes[reflection.foreign_key] = owner[reflection.ninja_model_primary_key]
110
+ if reflection.options[:as]
111
+ attributes[reflection.type] = owner.class.base_class.name
112
+ end
113
+ end
114
+ attributes
115
+ end
116
+ end
117
+
118
+ def set_owner_attributes(record)
119
+ creation_attributes.each { |key, value| record[key] = value }
120
+ end
121
+
122
+ def foreign_key_present?
123
+ false
124
+ end
125
+
126
+ def raise_on_type_mismatch(record)
127
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
128
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
129
+ raise ActiveRecord::AssociationTypeMismatch, message
130
+ end
131
+ end
132
+
133
+ def inverse_reflection_for(record)
134
+ reflection.inverse_of
135
+ end
136
+
137
+ def invertible_for?(record)
138
+ inverse_reflection_for(record)
139
+ end
140
+
141
+ def association_class
142
+ @reflection.klass
143
+ end
144
+ end
145
+ end
146
+ end