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,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Associations::BelongsToAssociation do
4
+ let(:user) { Factory(:user) }
5
+ let(:post) { Factory(:post, :user_id => user.id) }
6
+
7
+ context 'with an ActiveRecord parent' do
8
+ describe 'accessing the association directly' do
9
+ it 'should return an instance' do
10
+ post.user.should eql(user)
11
+ end
12
+ end
13
+
14
+ describe 'assigning an instance' do
15
+ it 'should update the associated id' do
16
+ post.user = user
17
+ post.user_id.should eql(user.id)
18
+ end
19
+ end
20
+
21
+ it 'should raise an exception when trying to assign the wrong class' do
22
+ lambda { post.user = Tag.new }.should raise_error
23
+ end
24
+ end
25
+
26
+ context 'with a NinjaModel parent' do
27
+ context 'and an ActiveRecord child' do
28
+ describe 'accessing the association directly' do
29
+ it 'should trigger a fetch' do
30
+ NinjaModel::Relation.any_instance.expects(:to_a).returns(post)
31
+ t = Tag.new(:post_id => post.id)
32
+ t.post
33
+ end
34
+
35
+ it 'should assign the proper predicates' do
36
+ t = Tag.new(:post_id => post.id)
37
+ predicates = t.association(:post).scoped.predicates
38
+ predicates.count.should eql(1)
39
+ predicates.first.attribute.should eql(:id)
40
+ predicates.first.meth.should eql(:eq)
41
+ predicates.first.value.should eql(post.id)
42
+ end
43
+ end
44
+ end
45
+
46
+ context 'and a NinjaModel child' do
47
+ describe 'accessing the association directly' do
48
+ it 'should trigger a fetch' do
49
+ NinjaModel::Relation.any_instance.expects(:to_a).returns(post)
50
+ c = Category.new(:post_id => post.id)
51
+ c.post
52
+ end
53
+
54
+ it 'should assign the proper predicates' do
55
+ c = Category.new(:post_id => post.id)
56
+ predicates = c.association(:post).scoped.predicates
57
+ predicates.count.should eql(1)
58
+ predicates.first.attribute.should eql(:id)
59
+ predicates.first.meth.should eql(:eq)
60
+ predicates.first.value.should eql(post.id)
61
+ end
62
+ end
63
+ end
64
+
65
+ it 'should raise an exception when trying to assign the wrong class' do
66
+ t = Tag.new
67
+ lambda { t.post = user }.should raise_error
68
+ end
69
+
70
+ it 'should properly update the ids when assigning a new instance' do
71
+ t = Factory(:tag)
72
+ t.post = post
73
+ t.post_id.should eql(post.id)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Associations::HasManyAssociation do
4
+ let(:user) { Factory(:user) }
5
+
6
+ context 'with an ActiveRecord parent' do
7
+
8
+ describe 'accessing the association directly' do
9
+ it 'should return a CollectionProxy' do
10
+ user.posts.should be_kind_of(NinjaModel::Associations::CollectionProxy)
11
+ end
12
+ end
13
+
14
+ describe 'first' do
15
+ it 'should trigger a fetch' do
16
+ NinjaModel::Relation.any_instance.expects(:to_a).returns([])
17
+ user.posts.first.should
18
+ end
19
+ end
20
+
21
+ describe 'adding a scope' do
22
+ it 'should return a NinjaModel::Relation' do
23
+ user.posts.published.should be_kind_of(NinjaModel::Relation)
24
+ end
25
+ it 'should have the correct predicates' do
26
+ predicates = user.posts.published.predicates
27
+ predicates.count.should eql(2)
28
+
29
+ predicates.first.attribute.should eql(:user_id)
30
+ predicates.first.meth.should eql(:eq)
31
+ predicates.first.value.should eql(user.id)
32
+
33
+ predicates.last.attribute.should eql(:published)
34
+ predicates.last.meth.should eql(:eq)
35
+ predicates.last.value.should eql(true)
36
+ end
37
+ end
38
+
39
+ describe 'building an associated object' do
40
+ it 'should return an unitialized object' do
41
+ user.posts.build.should be_kind_of(Post)
42
+ end
43
+ it 'should already belong to the user' do
44
+ user.posts.build.user.id.should eql(user.id)
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'with a NinjaModel parent' do
50
+
51
+ let(:post) { Factory(:post) }
52
+ let!(:tags) { FactoryGirl.create_list(:tag, 2, :post_id => 1) }
53
+
54
+ context 'and an ActiveRecord association' do
55
+ it 'should return an Array' do
56
+ post.tags.should be_kind_of(Array)
57
+ end
58
+
59
+ it 'should retrieve the proper associated objects' do
60
+ post.tags.count.should be > 0
61
+ end
62
+
63
+ it 'should allow chaining queries' do
64
+ post.tags.where(:name => 'Tag 2').count.should eql(1)
65
+ end
66
+
67
+ describe 'building an associated object' do
68
+ it 'should return an unitialized object' do
69
+ post.tags.build.should be_kind_of(Tag)
70
+ end
71
+ it 'should already belong to the Post' do
72
+ post.tags.build.post_id.should eql(post.id)
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'and a NinjaModel association' do
78
+ it 'should return a CollectionProxy' do
79
+ post.categories.should be_kind_of(NinjaModel::Associations::CollectionProxy)
80
+ end
81
+
82
+ it 'should assign the proper predicates' do
83
+ predicates = post.categories.scoped.predicates
84
+ predicates.count.should eql(1)
85
+ predicates.first.attribute.should eql(:post_id)
86
+ predicates.first.meth.should eql(:eq)
87
+ predicates.first.value.should eql(1)
88
+ end
89
+
90
+ it 'should allow chaining' do
91
+ predicates = post.categories.where(:name => 'foo').predicates
92
+ predicates.count.should eql(2)
93
+ predicates.first.attribute.should eql(:post_id)
94
+ predicates.first.meth.should eql(:eq)
95
+ predicates.first.value.should eql(1)
96
+ predicates.last.attribute.should eql(:name)
97
+ predicates.last.meth.should eql(:eq)
98
+ predicates.last.value.should eql('foo')
99
+ end
100
+
101
+ describe 'accessing the collection' do
102
+ it 'should trigger a fetch' do
103
+ NinjaModel::Relation.any_instance.expects(:to_a).returns([])
104
+ post.categories.count
105
+ end
106
+ end
107
+
108
+ describe 'building an associated object' do
109
+ it 'should return an unitialized object' do
110
+ post.categories.build.should be_kind_of(Category)
111
+ end
112
+ it 'should already belong to the user' do
113
+ post.categories.build.post_id.should eql(post.id)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Associations::HasOneAssociation do
4
+ let(:user) { Factory(:user) }
5
+ let(:post) { Factory(:post, :user_id => user.id) }
6
+ let(:bio) { Factory(:bio, :user_id => user.id) }
7
+
8
+ context 'with an ActiveRecord parent' do
9
+ describe 'accessing the association directly' do
10
+ it 'should trigger a fetch' do
11
+ NinjaModel::Relation.any_instance.expects(:to_a).returns([bio])
12
+ user.bio.name.should eql(bio.name)
13
+ end
14
+ end
15
+
16
+ it 'should assign the proper predicates' do
17
+ predicates = user.association(:bio).scoped.predicates
18
+ predicates.count.should eql(1)
19
+ predicates.first.attribute.should eql(:user_id)
20
+ predicates.first.meth.should eql(:eq)
21
+ predicates.first.value.should eql(user.id)
22
+ end
23
+ end
24
+
25
+ context 'with a NinjaModel parent' do
26
+ context 'and an ActiveRecord child' do
27
+ let!(:email_address) { Factory(:email_address, :bio_id => bio.id) }
28
+ describe 'accessing the association directly' do
29
+ it 'should retrieve the record' do
30
+ bio.email_address.should be_kind_of(EmailAddress)
31
+ end
32
+ end
33
+
34
+ it 'should allow building the association' do
35
+ email = bio.build_email_address
36
+ email.should be_kind_of(EmailAddress)
37
+ email.bio_id.should eql(bio.id)
38
+ end
39
+
40
+ it 'should allow creating the association' do
41
+ bio.create_email_address(:email => 'foo@bar.com').should be_true
42
+ end
43
+ end
44
+
45
+ context 'and a NinjaModel child' do
46
+ let(:body) { Factory(:body, :post_id => post.id) }
47
+ describe 'accessing the association directly' do
48
+ it 'should trigger a fetch' do
49
+ NinjaModel::Relation.any_instance.expects(:to_a).returns([body])
50
+ post.body
51
+ end
52
+
53
+ it 'should assign the proper predicates' do
54
+ predicates = post.association(:body).scoped.predicates
55
+ predicates.count.should eql(1)
56
+ predicates.first.attribute.should eql(:post_id)
57
+ predicates.first.meth.should eql(:eq)
58
+ predicates.first.value.should eql(post.id)
59
+ end
60
+ end
61
+
62
+ it 'should allow building the association' do
63
+ #
64
+ # We have to stub to_a here because building will try and retrieve the
65
+ # existing record to remove ownership
66
+ #
67
+ NinjaModel::Relation.any_instance.stubs(:to_a).returns([])
68
+ body = post.build_body
69
+ body.should be_kind_of(Body)
70
+ body.post_id.should eql(post.id)
71
+ end
72
+
73
+ it 'should allow creating the association' do
74
+ NinjaModel::Relation.any_instance.stubs(:to_a).returns([])
75
+ Body.any_instance.stubs(:create).returns(true)
76
+ post.create_body(:text => 'Foobar')
77
+ end
78
+ end
79
+ end
80
+ end
@@ -5,8 +5,8 @@ describe NinjaModel::AttributeMethods do
5
5
  attribute :test, :string
6
6
  end
7
7
  subject { AttributeModel.new }
8
- specify { subject.attribute_method?(:test).should be_true }
9
- specify { subject.attribute_method?(:invalid).should be_false }
8
+ specify { subject.send(:attribute_method?, 'test').should be_true }
9
+ specify { subject.send(:attribute_method?, 'invalid').should be_false }
10
10
 
11
11
  it 'should return a list of valid attribute names' do
12
12
  AttributeModel.attribute_names.should eql(['test'])
@@ -8,6 +8,10 @@ describe NinjaModel::Attribute do
8
8
  RSpec::Matchers.define :convert do |input|
9
9
  chain :to do |expected|
10
10
  @expected = expected
11
+
12
+ failure_message_for_should do |attr|
13
+ "convert(#{input.inspect}) should be #{@expected.inspect}, but got #{@actual.inspect}"
14
+ end
11
15
  end
12
16
 
13
17
  match do |attr|
File without changes
@@ -14,15 +14,13 @@ describe NinjaModel::Reflection do
14
14
 
15
15
  describe 'create_reflection' do
16
16
  it 'should store the reflections on the class' do
17
- @klass = Class.new(NinjaModel::Base)
18
- @reflection = @klass.create_reflection(:has_one, :target_model, {}, @klass)
19
- @klass.reflections.should eql(:target_model => @reflection)
17
+ @reflection = ReflectionModel.create_reflection(:has_one, :target_model, {}, TargetModel)
18
+ ReflectionModel.reflections.should eql(:target_model => @reflection)
20
19
  end
21
20
 
22
21
  it 'should return a reflection for a particular association' do
23
- @klass = Class.new(NinjaModel::Base)
24
- @reflection = @klass.create_reflection(:has_one, :target_model, {}, @klass)
25
- @klass.reflect_on_association(:target_model).should eql(@reflection)
22
+ @reflection = ReflectionModel.create_reflection(:has_one, :target_model, {}, TargetModel)
23
+ ReflectionModel.reflect_on_association(:target_model).should eql(@reflection)
26
24
  end
27
25
 
28
26
  context 'has_one' do
@@ -30,7 +28,7 @@ describe NinjaModel::Reflection do
30
28
  its(:class_name) { should eql('TargetModel') }
31
29
  its(:klass) { should eql(TargetModel) }
32
30
  its(:collection?) { should be_false }
33
- its(:primary_key_name) { should eql('reflection_model_id') }
31
+ its(:foreign_key) { should eql('reflection_model_id') }
34
32
  its(:association_foreign_key) { should eql('target_model_id') }
35
33
  its(:belongs_to?) { should be_false }
36
34
  end
@@ -40,7 +38,7 @@ describe NinjaModel::Reflection do
40
38
  its(:class_name) { should eql('TargetModel') }
41
39
  its(:klass) { should eql(TargetModel) }
42
40
  its(:collection?) { should be_true }
43
- its(:primary_key_name) { should eql('reflection_model_id') }
41
+ its(:foreign_key) { should eql('reflection_model_id') }
44
42
  its(:association_foreign_key) { should eql('target_model_id') }
45
43
  its(:belongs_to?) { should be_false }
46
44
  end
@@ -50,7 +48,7 @@ describe NinjaModel::Reflection do
50
48
  its(:class_name) { should eql('ReflectionModel') }
51
49
  its(:klass) { should eql(ReflectionModel) }
52
50
  its(:collection?) { should be_false }
53
- its(:primary_key_name) { should eql('reflection_model_id') }
51
+ its(:foreign_key) { should eql('reflection_model_id') }
54
52
  its(:association_foreign_key) { should eql('reflection_model_id') }
55
53
  its(:belongs_to?) { should be_true }
56
54
  end
@@ -0,0 +1,8 @@
1
+ class Bio < NinjaModel::Base
2
+ attribute :id, :integer
3
+ attribute :user_id, :integer
4
+ attribute :name, :string
5
+
6
+ belongs_to :user
7
+ has_one :email_address
8
+ end
@@ -0,0 +1,7 @@
1
+ class Body < NinjaModel::Base
2
+ attribute :id, :integer, :primary_key => true
3
+ attribute :post_id, :integer
4
+ attribute :text, :string
5
+
6
+ belongs_to :post
7
+ end
@@ -0,0 +1,7 @@
1
+ class Category < NinjaModel::Base
2
+ attribute :id, :integer, :primary_key => true
3
+ attribute :post_id, :integer
4
+ attribute :name, :string
5
+
6
+ belongs_to :post
7
+ end
@@ -0,0 +1,3 @@
1
+ class EmailAddress < ActiveRecord::Base
2
+ belongs_to :bio
3
+ end
@@ -0,0 +1,13 @@
1
+ class Post < NinjaModel::Base
2
+ attribute :id, :integer, :primary_key => true
3
+ attribute :user_id, :integer
4
+ attribute :title, :string
5
+ attribute :published, :boolean
6
+
7
+ scope :published, where(:published => true)
8
+
9
+ belongs_to :user
10
+ has_one :body
11
+ has_many :tags
12
+ has_many :categories
13
+ end
@@ -0,0 +1,3 @@
1
+ class Tag < ActiveRecord::Base
2
+ belongs_to :post
3
+ end
@@ -0,0 +1,4 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :posts
3
+ has_one :bio
4
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,22 +1,55 @@
1
+ require 'rubygems'
2
+ require 'bundler'
1
3
  require 'simplecov'
2
4
  SimpleCov.start do
3
5
  add_filter 'spec'
4
6
  end
5
- #Bundler.require(:default)
6
- require 'rspec/core'
7
- require 'ninja-model'
8
-
9
- Dir[File.join(File.expand_path('../', __FILE__), 'support/**/*.rb')].each { |f| require f }
7
+ Bundler.require(:default, :test)
10
8
 
11
9
  RSpec.configure do |config|
12
10
  config.mock_with :mocha
13
11
  end
14
12
 
13
+
14
+ def init_active_record
15
+ db_root = File.join(File.expand_path('../', __FILE__), 'db')
16
+ ActiveRecord::Base.configurations['test'] = {
17
+ 'adapter' => 'sqlite3',
18
+ 'database' => File.join(db_root, 'test.sqlite3'),
19
+ 'pool' => 5,
20
+ 'timeout' => 5000
21
+ }
22
+ ActiveRecord::Base.establish_connection(:test)
23
+
24
+ schema_file = File.join(File.expand_path('../', __FILE__), 'db', 'schema.rb')
25
+ load schema_file
26
+ end
27
+
28
+ init_active_record
29
+
15
30
  class DummyLogger
16
31
  def debug(*args)
32
+ #puts "*** DEBUG ***"
33
+ #puts args
17
34
  end
18
35
  def warn(*args)
36
+ puts "*** WARNING ***"
37
+ puts args
38
+ end
39
+
40
+ def error(*args)
41
+ puts "*** ERROR ***"
42
+ puts args
43
+ end
44
+
45
+ def debug?
46
+ true
19
47
  end
20
48
  end
21
49
 
22
50
  NinjaModel.set_logger(DummyLogger.new)
51
+ ActiveRecord::Base.logger = DummyLogger.new
52
+
53
+ ActiveSupport::Dependencies.autoload_paths << File.join(File.expand_path('../', __FILE__), 'models')
54
+
55
+ Dir[File.join(File.expand_path('../', __FILE__), 'support/**/*.rb')].each { |f| require f }