ninja-model 0.4.2 → 0.5.1

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 (54) hide show
  1. data/.gitignore +19 -0
  2. data/Rakefile +0 -7
  3. data/autotest/discover.rb +1 -0
  4. data/lib/ninja_model.rb +22 -26
  5. data/lib/ninja_model/adapters.rb +33 -43
  6. data/lib/ninja_model/adapters/abstract_adapter.rb +2 -10
  7. data/lib/ninja_model/adapters/adapter_manager.rb +17 -10
  8. data/lib/ninja_model/adapters/adapter_pool.rb +15 -17
  9. data/lib/ninja_model/adapters/adapter_specification.rb +3 -3
  10. data/lib/ninja_model/associations.rb +25 -106
  11. data/lib/ninja_model/associations/association_proxy.rb +119 -1
  12. data/lib/ninja_model/associations/belongs_to_association.rb +5 -1
  13. data/lib/ninja_model/attribute.rb +130 -0
  14. data/lib/ninja_model/{attributes.rb → attribute_methods.rb} +21 -42
  15. data/lib/ninja_model/base.rb +23 -20
  16. data/lib/ninja_model/callbacks.rb +2 -11
  17. data/lib/ninja_model/identity.rb +6 -11
  18. data/lib/ninja_model/persistence.rb +15 -30
  19. data/lib/ninja_model/predicate.rb +4 -4
  20. data/lib/ninja_model/rails_ext/active_record.rb +187 -0
  21. data/lib/ninja_model/railtie.rb +14 -8
  22. data/lib/ninja_model/reflection.rb +7 -14
  23. data/lib/ninja_model/relation.rb +5 -3
  24. data/lib/ninja_model/relation/finder_methods.rb +4 -8
  25. data/lib/ninja_model/relation/spawn_methods.rb +1 -1
  26. data/lib/ninja_model/validation.rb +6 -23
  27. data/lib/ninja_model/version.rb +1 -1
  28. data/ninja-model.gemspec +28 -0
  29. data/spec/ninja_model/adapters/abstract_adapter_spec.rb +45 -0
  30. data/spec/ninja_model/adapters/adapter_manager_spec.rb +69 -0
  31. data/spec/ninja_model/adapters/adapter_pool_spec.rb +210 -48
  32. data/spec/ninja_model/adapters_spec.rb +77 -0
  33. data/spec/ninja_model/attribute_methods_spec.rb +95 -0
  34. data/spec/ninja_model/attribute_spec.rb +129 -0
  35. data/spec/ninja_model/base_spec.rb +4 -52
  36. data/spec/ninja_model/identity_spec.rb +16 -32
  37. data/spec/ninja_model/persistence_spec.rb +130 -4
  38. data/spec/ninja_model/predicate_spec.rb +40 -6
  39. data/spec/ninja_model/query_methods_spec.rb +76 -74
  40. data/spec/ninja_model/reflection_spec.rb +63 -0
  41. data/spec/ninja_model/relation_spec.rb +213 -20
  42. data/spec/ninja_model/symbol_spec.rb +19 -0
  43. data/spec/ninja_model/validation_spec.rb +18 -0
  44. data/spec/spec_helper.rb +9 -0
  45. data/spec/support/matchers/convert.rb +30 -0
  46. metadata +85 -63
  47. data/lib/ninja_model/associations/active_record_proxy.rb +0 -53
  48. data/lib/ninja_model/associations/ninja_model_proxy.rb +0 -46
  49. data/lib/ninja_model/configuration.rb +0 -20
  50. data/lib/ninja_model/errors.rb +0 -5
  51. data/lib/ninja_model/log_subscriber.rb +0 -18
  52. data/lib/ninja_model/scoping.rb +0 -50
  53. data/spec/ninja_model/attributes_spec.rb +0 -85
  54. data/spec/ninja_model/scoping_spec.rb +0 -40
@@ -29,7 +29,7 @@ module NinjaModel
29
29
  end
30
30
  merged_relation.predicates = merged_predicates
31
31
 
32
- Relation::SINGLE_VALUE_ATTRS.reject { |m| m == :lock }.each do |method|
32
+ Relation::SINGLE_VALUE_ATTRS.each do |method|
33
33
  value = r.send(:"#{method}_value")
34
34
  merged_relation.send(:"#{method}_value=", value) unless value.nil?
35
35
  end
@@ -1,13 +1,14 @@
1
- require 'active_model'
2
-
3
1
  module NinjaModel
4
- module Validation
5
- extend ActiveSupport::Concern
2
+ class Base
6
3
  include ActiveModel::Validations
4
+ define_model_callbacks :validation
5
+ end
6
+
7
+ module Validation
7
8
 
8
9
  def save(options={})
9
10
  run_callbacks :validation do
10
- perform_validations(options) ? super : false
11
+ valid?(options.is_a?(Hash) ? options[:context] : nil) ? super : false
11
12
  end
12
13
  end
13
14
 
@@ -16,23 +17,5 @@ module NinjaModel
16
17
  output = super(context)
17
18
  errors.empty? && output
18
19
  end
19
-
20
- protected
21
-
22
- def perform_validations(options={})
23
- perform_validation = case options
24
- when Hash
25
- options[:validate] != false
26
- else
27
- ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
28
- options
29
- end
30
-
31
- if perform_validation
32
- valid?(options.is_a?(Hash) ? options[:context] : nil)
33
- else
34
- true
35
- end
36
- end
37
20
  end
38
21
  end
@@ -1,3 +1,3 @@
1
1
  module NinjaModel
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ninja_model/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{ninja-model}
7
+ s.version = NinjaModel::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.author = %q{Josh Williams}
10
+ s.email = %q{theprime@codingprime.com}
11
+ s.homepage = %q{http://github.com/t3hpr1m3/ninja-model.git}
12
+ s.summary = %q{Pseudo-ORM for Ruby}
13
+ s.description = %q{Pseudo-ORM for Ruby/Rails with an ActiveRecord-like interface}
14
+
15
+ s.rubyforge_project = s.name
16
+
17
+ s.files = `git ls-files`.split("\n") - ["Gemfile.lock"]
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency 'activerecord', '~> 3.0.3'
22
+
23
+ s.add_development_dependency 'rspec', '~> 2.2.0'
24
+ s.add_development_dependency 'mocha', '~> 0.9.8'
25
+ s.add_development_dependency 'rcov', '~> 0.9.9'
26
+ s.add_development_dependency 'nokogiri', '~> 1.4.4'
27
+ s.add_development_dependency 'autotest', '~> 4.4.6'
28
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Adapters::AbstractAdapter do
4
+ subject { NinjaModel::Adapters::AbstractAdapter.new({}) }
5
+ it { should respond_to(:reconnect!) }
6
+ it { should respond_to(:disconnect!) }
7
+ it { should respond_to(:reset!) }
8
+ it { should respond_to(:verify!) }
9
+ it { should respond_to(:create) }
10
+ it { should respond_to(:read) }
11
+ it { should respond_to(:update) }
12
+ it { should respond_to(:destroy) }
13
+ its(:adapter_name) { should eql('Abstract') }
14
+ its(:persistent_connection?) { should be_true }
15
+ its(:active?) { should be_false }
16
+ specify { subject.create({}).should be_false }
17
+ specify { subject.read({}).should be_nil }
18
+ specify { subject.update({}).should be_false }
19
+ specify { subject.destroy({}).should be_false }
20
+
21
+ it 'should be active after reconnect' do
22
+ subject.reconnect!
23
+ subject.active?.should be_true
24
+ end
25
+
26
+ describe 'verify!' do
27
+ it 'should call reconnect! when inactive' do
28
+ subject.stubs(:active?).returns(false)
29
+ subject.expects(:reconnect!)
30
+ subject.verify!
31
+ end
32
+
33
+ it 'should not call reconnect! when active' do
34
+ subject.stubs(:active?).returns(true)
35
+ subject.expects(:reconnect!).never
36
+ subject.verify!
37
+ end
38
+ end
39
+
40
+ it 'should be inactive after disconnect' do
41
+ subject.reconnect!
42
+ subject.disconnect!
43
+ subject.active?.should be_false
44
+ end
45
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Adapters::AdapterManager do
4
+ class DummyAdapter < NinjaModel::Adapters::AbstractAdapter; end
5
+ class TestModel < NinjaModel::Base; end
6
+ class ChildModel < TestModel; end
7
+
8
+ before {
9
+ @manager = NinjaModel::Adapters::AdapterManager.new
10
+ @manager.class.register_adapter_class(:dummy, DummyAdapter)
11
+ }
12
+ subject { @manager }
13
+
14
+ it 'should remember registered adapters' do
15
+ subject.class.registered?(:dummy).should be_true
16
+ end
17
+
18
+ describe 'creating an adapter' do
19
+ before { @manager.create_adapter('TestModel', NinjaModel::Adapters::AdapterSpecification.new({}, :dummy)) }
20
+ it 'should add the adapter to the pool' do
21
+ @manager.retrieve_adapter_pool(TestModel).spec.name.should eql(:dummy)
22
+ end
23
+ it 'should return the adapter for descendent classes' do
24
+ @manager.retrieve_adapter_pool(ChildModel).spec.name.should eql(:dummy)
25
+ end
26
+ end
27
+
28
+ describe 'removing an adapter' do
29
+ before {
30
+ @spec = NinjaModel::Adapters::AdapterSpecification.new({:foo => 'bar'}, :dummy)
31
+ @manager.create_adapter('TestModel', @spec)
32
+ }
33
+ it 'should remove the adapter from the pool' do
34
+ @manager.remove_adapter(TestModel)
35
+ @manager.retrieve_adapter_pool(TestModel).should be_nil
36
+ end
37
+ it 'should cause retrieve_adapter to raise an error' do
38
+ @manager.remove_adapter(TestModel)
39
+ lambda { @manager.retrieve_adapter(TestModel) }.should raise_error(StandardError)
40
+ end
41
+ it 'should return the spec that created it' do
42
+ @manager.remove_adapter(TestModel).should eql({:foo => 'bar'})
43
+ end
44
+ end
45
+
46
+ describe 'release_active_adapters!' do
47
+ before {
48
+ @spec = NinjaModel::Adapters::AdapterSpecification.new({:foo => 'bar'}, :dummy)
49
+ @manager.create_adapter('TestModel', @spec)
50
+ }
51
+ it 'should call release_instance on all pools' do
52
+ @pool = @manager.adapter_pools['TestModel']
53
+ @pool.expects(:release_instance)
54
+ @manager.release_active_adapters!
55
+ end
56
+ end
57
+
58
+ describe 'release_all_adapters!' do
59
+ before {
60
+ @spec = NinjaModel::Adapters::AdapterSpecification.new({:foo => 'bar'}, :dummy)
61
+ @manager.create_adapter('TestModel', @spec)
62
+ }
63
+ it 'should call shutdown! on all pools' do
64
+ @pool = @manager.adapter_pools['TestModel']
65
+ @pool.expects(:shutdown!)
66
+ @manager.release_all_adapters!
67
+ end
68
+ end
69
+ end
@@ -1,72 +1,234 @@
1
1
  require 'spec_helper'
2
- require 'ninja_model'
3
2
 
4
3
  describe NinjaModel::Adapters::AdapterPool do
5
- before(:each) do
6
- @spec = mock('AdapterSpecification') do
7
- stubs(:adapter_method).returns(:dummy_adapter)
8
- stubs(:config)
9
- end
4
+ class DummyAdapter < NinjaModel::Adapters::AbstractAdapter; end
5
+ before {
6
+ @spec = NinjaModel::Adapters::AdapterSpecification.new({}, :dummy)
7
+ NinjaModel::Base.register_adapter(:dummy, DummyAdapter)
8
+ @pool = NinjaModel::Adapters::AdapterPool.new(@spec)
9
+ }
10
+ subject { @pool }
11
+ its(:spec) { should eql(@spec) }
12
+ its(:instances) { should be_empty }
13
+ its(:connected?) { should be_false }
14
+ its(:current_instance_id) { should eql(Thread.current.object_id) }
10
15
 
11
- @adapter = mock('AbstractAdapter') do
12
- stubs(:verify!)
16
+ describe 'obtaining an adapter instance' do
17
+ it 'should store the instance for a given thread' do
18
+ subject.instance
19
+ subject.expects(:checkout).never
20
+ subject.instance
13
21
  end
22
+ end
14
23
 
15
- @pool = NinjaModel::Adapters::AdapterPool.new(@spec)
16
- @thread = mock('thread') do
17
- stubs('object_id').returns(123)
24
+ describe 'releasing an adapter instance' do
25
+ it 'should call checkout when obtaining another instance' do
26
+ subject.expects(:checkout).twice
27
+ subject.instance
28
+ subject.release_instance
29
+ subject.instance
18
30
  end
19
- Thread.stubs(:current).returns(@thread)
20
- NinjaModel::Base.stubs(:dummy_adapter).returns(@adapter)
21
31
  end
22
32
 
23
- it 'should properly respond to connected?' do
24
- @pool.connected?.should be_false
25
- @pool.instance
26
- @pool.connected?.should be_true
33
+ describe 'with_instance' do
34
+ it 'should yield an instance' do
35
+ @inst = nil
36
+ subject.with_instance do |inst|
37
+ @inst = inst
38
+ end
39
+ @inst.should be_kind_of(DummyAdapter)
40
+ end
41
+ it 'should release the instance when complete' do
42
+ subject.expects(:release_instance).with(subject.send :current_instance_id)
43
+ subject.with_instance do |inst|
44
+ end
45
+ end
27
46
  end
28
47
 
29
- it 'should initialize from an AdapterSpecification' do
30
- spec = mock('AdapterSpecification')
31
- NinjaModel::Adapters::AdapterPool.new(spec).spec.should eql(spec)
48
+ describe 'shutdown!' do
49
+ it 'should checkin all assigned instances' do
50
+ @inst = subject.instance
51
+ subject.expects(:checkin).with(@inst)
52
+ subject.shutdown!
53
+ end
54
+ it 'should call disconnect on all instances' do
55
+ @inst = subject.instance
56
+ @inst.expects(:disconnect!)
57
+ subject.shutdown!
58
+ end
32
59
  end
33
60
 
34
- it 'should return a valid instance_id' do
35
- (@pool.send :current_instance_id).should eql(@thread.object_id)
61
+ describe 'clear_stale_cached_instances!' do
62
+ it 'should reap any instances for dead threads' do
63
+ @thread = mock('Thread') do
64
+ stubs(:object_id).returns(Thread.current.object_id)
65
+ stubs(:alive?).returns(false)
66
+ end
67
+ Thread.stubs(:list).returns([@thread])
68
+ @inst = subject.instance
69
+ subject.expects(:checkin).with(@inst)
70
+ subject.send :clear_stale_cached_instances!
71
+ end
72
+ it 'should not reap instances for live threads' do
73
+ @thread = mock('Thread') do
74
+ stubs(:object_id).returns(Thread.current.object_id)
75
+ stubs(:alive?).returns(true)
76
+ end
77
+ Thread.stubs(:list).returns([@thread])
78
+ @inst = subject.instance
79
+ subject.expects(:checkin).with(@inst).never
80
+ subject.send :clear_stale_cached_instances!
81
+ end
36
82
  end
37
83
 
38
- describe 'when creating an instance' do
39
- it 'should create a valid instance' do
40
- (@pool.send :new_instance).should eql(@adapter)
84
+ describe 'checkout' do
85
+ subject { NinjaModel::Adapters::AdapterPool.new(@spec) }
86
+ context 'with no current instances' do
87
+ it 'should checkout a new instance' do
88
+ subject.expects(:checkout_new_instance).returns('foo')
89
+ subject.send(:checkout)
90
+ end
91
+ it 'should store the instance' do
92
+ subject.send(:checkout)
93
+ subject.instances.count.should eql(1)
94
+ end
41
95
  end
42
96
 
43
- it 'should store the instance' do
44
- @pool.stubs(:new_instance).returns(@adapter)
45
- @pool.instance
46
- @pool.instances.should include(@adapter)
97
+ context 'with 1 existing instance' do
98
+ context 'that is already assigned' do
99
+ it 'should checkout a new instance' do
100
+ threads = []
101
+ subject.expects(:new_instance).twice.returns(NinjaModel::Adapters::AdapterManager.registered[subject.spec.name].new(subject.spec.config))
102
+ 2.times do |idx|
103
+ threads << Thread.new do
104
+ subject.send(:checkout)
105
+ end
106
+ end
107
+ threads.each { |t| t.join }
108
+ end
109
+ end
110
+ context 'that is not assigned' do
111
+ it 'should checkout the existing instance' do
112
+ subject.expects(:checkout_existing_instance).returns('existing')
113
+ subject.instance
114
+ subject.release_instance
115
+ subject.instances.count.should eql(1)
116
+ subject.send(:checkout)
117
+ end
118
+ end
47
119
  end
48
120
 
49
- it 'should assign the instance to the current_thread' do
50
- @pool.stubs(:new_instance).returns(@adapter)
51
- @pool.instance
52
- @pool.stubs(:checkout).returns("new_instance")
53
- @pool.instance.should_not eql("new_instance")
54
- @pool.instance.should eql(@adapter)
55
- end
121
+ context 'with the maximum number of instances' do
122
+ context 'with an available instance' do
123
+ it 'should successfully check out an instance' do
124
+ subject.instance
125
+ threads = []
126
+ 4.times do |idx|
127
+ threads << Thread.new do
128
+ subject.instance
129
+ end
130
+ end
131
+ threads.each { |t| t.join }
132
+ subject.release_instance
133
+ subject.send(:checkout).should_not be_nil
134
+ end
135
+ end
56
136
 
57
- it 'should reuse an unallocated instance' do
58
- @pool.stubs(:current_instance_id).returns(123)
59
- inst = @pool.instance
60
- @pool.release_instance(123)
61
- @pool.instance.should eql(inst)
62
- end
63
- end
137
+ context 'with no available instances' do
138
+ it 'should raise a ConnectionTimeoutError' do
139
+ threads = []
140
+ 5.times do |idx|
141
+ threads << Thread.new do
142
+ subject.instance
143
+ end
144
+ end
145
+ threads.each { |t| t.join }
146
+ subject.stubs(:clear_stale_cached_instances!)
147
+ lambda { subject.instance }.should raise_error(NinjaModel::ConnectionTimeoutError)
148
+ end
149
+ end
64
150
 
65
- it 'should properly allocate/deallocate instances' do
66
- @pool.stubs(:current_instance_id).returns(123)
67
- @pool.expects(:checkout).returns(@adapter)
68
- @pool.expects(:checkin)
69
- @pool.with_instance do |instance|
151
+ context 'with instances that can be reaped' do
152
+ it 'should return an existing instance' do
153
+ threads = []
154
+ 5.times do |idx|
155
+ threads << Thread.new do
156
+ subject.instance
157
+ end
158
+ end
159
+ threads.each { |t| t.join }
160
+ subject.expects(:checkout_existing_instance).returns('foo')
161
+ subject.instance
162
+ end
163
+ end
70
164
  end
165
+
71
166
  end
167
+ # before(:each) do
168
+ # @spec = mock('AdapterSpecification') do
169
+ # stubs(:adapter_method).returns(:dummy_adapter)
170
+ # stubs(:config)
171
+ # end
172
+ #
173
+ # @adapter = mock('AbstractAdapter') do
174
+ # stubs(:verify!)
175
+ # end
176
+ #
177
+ # @pool = NinjaModel::Adapters::AdapterPool.new(@spec)
178
+ # @thread = mock('thread') do
179
+ # stubs('object_id').returns(123)
180
+ # end
181
+ # Thread.stubs(:current).returns(@thread)
182
+ # NinjaModel::Base.stubs(:dummy_adapter).returns(@adapter)
183
+ # end
184
+ #
185
+ # it 'should properly respond to connected?' do
186
+ # @pool.connected?.should be_false
187
+ # @pool.instance
188
+ # @pool.connected?.should be_true
189
+ # end
190
+ #
191
+ # it 'should initialize from an AdapterSpecification' do
192
+ # spec = mock('AdapterSpecification')
193
+ # NinjaModel::Adapters::AdapterPool.new(spec).spec.should eql(spec)
194
+ # end
195
+ #
196
+ # it 'should return a valid instance_id' do
197
+ # (@pool.send :current_instance_id).should eql(@thread.object_id)
198
+ # end
199
+ #
200
+ # describe 'when creating an instance' do
201
+ # it 'should create a valid instance' do
202
+ # (@pool.send :new_instance).should eql(@adapter)
203
+ # end
204
+ #
205
+ # it 'should store the instance' do
206
+ # @pool.stubs(:new_instance).returns(@adapter)
207
+ # @pool.instance
208
+ # @pool.instances.should include(@adapter)
209
+ # end
210
+ #
211
+ # it 'should assign the instance to the current_thread' do
212
+ # @pool.stubs(:new_instance).returns(@adapter)
213
+ # @pool.instance
214
+ # @pool.stubs(:checkout).returns("new_instance")
215
+ # @pool.instance.should_not eql("new_instance")
216
+ # @pool.instance.should eql(@adapter)
217
+ # end
218
+ #
219
+ # it 'should reuse an unallocated instance' do
220
+ # @pool.stubs(:current_instance_id).returns(123)
221
+ # inst = @pool.instance
222
+ # @pool.release_instance(123)
223
+ # @pool.instance.should eql(inst)
224
+ # end
225
+ # end
226
+ #
227
+ # it 'should properly allocate/deallocate instances' do
228
+ # @pool.stubs(:current_instance_id).returns(123)
229
+ # @pool.expects(:checkout).returns(@adapter)
230
+ # @pool.expects(:checkin)
231
+ # @pool.with_instance do |instance|
232
+ # end
233
+ # end
72
234
  end