hydra_attribute 0.4.2 → 0.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +6 -5
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile +1 -1
  5. data/README.md +3 -3
  6. data/Rakefile +2 -7
  7. data/gemfiles/activerecord-3.2.gemfile +5 -0
  8. data/hydra_attribute.gemspec +6 -7
  9. data/lib/hydra_attribute.rb +17 -18
  10. data/lib/hydra_attribute/active_record.rb +34 -13
  11. data/lib/hydra_attribute/active_record/association_preloader.rb +47 -28
  12. data/lib/hydra_attribute/active_record/attribute_methods.rb +29 -140
  13. data/lib/hydra_attribute/active_record/mass_assignment_security.rb +39 -0
  14. data/lib/hydra_attribute/active_record/migration.rb +4 -4
  15. data/lib/hydra_attribute/active_record/relation.rb +6 -7
  16. data/lib/hydra_attribute/active_record/relation/query_methods.rb +28 -18
  17. data/lib/hydra_attribute/hydra_attribute.rb +12 -49
  18. data/lib/hydra_attribute/hydra_attribute_set.rb +67 -0
  19. data/lib/hydra_attribute/hydra_entity.rb +110 -0
  20. data/lib/hydra_attribute/hydra_entity_attribute_association.rb +155 -0
  21. data/lib/hydra_attribute/hydra_set.rb +24 -26
  22. data/lib/hydra_attribute/hydra_value.rb +210 -0
  23. data/lib/hydra_attribute/identity_map.rb +18 -0
  24. data/lib/hydra_attribute/middleware/identity_map.rb +15 -0
  25. data/lib/hydra_attribute/migrator.rb +24 -21
  26. data/lib/hydra_attribute/model.rb +47 -0
  27. data/lib/hydra_attribute/model/cacheable.rb +207 -0
  28. data/lib/hydra_attribute/model/dirty.rb +39 -0
  29. data/lib/hydra_attribute/model/has_many_through.rb +168 -0
  30. data/lib/hydra_attribute/model/identity_map.rb +59 -0
  31. data/lib/hydra_attribute/model/mediator.rb +89 -0
  32. data/lib/hydra_attribute/model/notifiable.rb +23 -0
  33. data/lib/hydra_attribute/model/persistence.rb +424 -0
  34. data/lib/hydra_attribute/model/validations.rb +40 -0
  35. data/lib/hydra_attribute/version.rb +1 -1
  36. data/spec/environments/mysql.rb +23 -0
  37. data/spec/environments/postgresql.rb +23 -0
  38. data/spec/environments/sqlite.rb +12 -0
  39. data/spec/fixtures/category.rb +8 -0
  40. data/spec/fixtures/product.rb +8 -0
  41. data/spec/fixtures/product_black_list.rb +13 -0
  42. data/spec/fixtures/product_white_list.rb +13 -0
  43. data/spec/hydra_attribute/active_record/attribute_methods_spec.rb +23 -28
  44. data/spec/hydra_attribute/active_record/mass_assignment_security_spec.rb +41 -0
  45. data/spec/hydra_attribute/active_record_spec.rb +577 -0
  46. data/spec/hydra_attribute/hydra_attribute_set_spec.rb +651 -0
  47. data/spec/hydra_attribute/hydra_attribute_spec.rb +208 -10
  48. data/spec/hydra_attribute/hydra_entity_attribute_association_spec.rb +216 -0
  49. data/spec/hydra_attribute/hydra_entity_spec.rb +71 -0
  50. data/spec/hydra_attribute/hydra_set_spec.rb +51 -10
  51. data/spec/hydra_attribute/hydra_value_spec.rb +286 -0
  52. data/spec/hydra_attribute/identity_map_spec.rb +47 -0
  53. data/spec/hydra_attribute/migrator_spec.rb +411 -0
  54. data/spec/hydra_attribute/model/cacheable_spec.rb +106 -0
  55. data/spec/hydra_attribute/model/has_many_through_spec.rb +132 -0
  56. data/spec/hydra_attribute/model/identity_map_spec.rb +39 -0
  57. data/spec/hydra_attribute/model/mediator_spec.rb +62 -0
  58. data/spec/hydra_attribute/model/persistence_spec.rb +550 -0
  59. data/spec/hydra_attribute/model_spec.rb +39 -0
  60. data/spec/hydra_attribute_spec.rb +36 -0
  61. data/spec/spec_helper.rb +10 -42
  62. metadata +76 -100
  63. data/Appraisals +0 -7
  64. data/cucumber.yml +0 -1
  65. data/features/entity/create.feature +0 -145
  66. data/features/entity/destroy.feature +0 -111
  67. data/features/entity/new.feature +0 -121
  68. data/features/entity/update.feature +0 -147
  69. data/features/hydra_attribute/create.feature +0 -30
  70. data/features/hydra_attribute/destroy.feature +0 -26
  71. data/features/hydra_attribute/update.feature +0 -36
  72. data/features/hydra_set/destroy.feature +0 -31
  73. data/features/migrations/create_and_drop.feature +0 -165
  74. data/features/migrations/migrate_and_rollback.feature +0 -211
  75. data/features/relation/query_methods/group.feature +0 -42
  76. data/features/relation/query_methods/order.feature +0 -67
  77. data/features/relation/query_methods/reorder.feature +0 -29
  78. data/features/relation/query_methods/reverse_order.feature +0 -29
  79. data/features/relation/query_methods/select.feature +0 -50
  80. data/features/relation/query_methods/where.feature +0 -115
  81. data/features/step_definitions/connections.rb +0 -65
  82. data/features/step_definitions/model_steps.rb +0 -136
  83. data/features/step_definitions/query_methods.rb +0 -48
  84. data/features/step_definitions/record_steps.rb +0 -93
  85. data/features/support/env.rb +0 -38
  86. data/features/support/world.rb +0 -61
  87. data/lib/hydra_attribute/active_record/association.rb +0 -113
  88. data/lib/hydra_attribute/active_record/reflection.rb +0 -16
  89. data/lib/hydra_attribute/association_builder.rb +0 -69
  90. data/lib/hydra_attribute/builder.rb +0 -37
  91. data/lib/hydra_attribute/entity_callbacks.rb +0 -26
  92. data/lib/hydra_attribute/hydra_attribute_methods.rb +0 -226
  93. data/lib/hydra_attribute/hydra_methods.rb +0 -528
  94. data/lib/hydra_attribute/hydra_set_methods.rb +0 -95
  95. data/lib/hydra_attribute/hydra_value_methods.rb +0 -21
  96. data/lib/hydra_attribute/memoizable.rb +0 -37
  97. data/spec/hydra_attribute/active_record/relation/query_methods_spec.rb +0 -31
  98. data/spec/hydra_attribute/hydra_attribute_methods_spec.rb +0 -458
  99. data/spec/hydra_attribute/hydra_methods_spec.rb +0 -456
  100. data/spec/hydra_attribute/hydra_set_methods_spec.rb +0 -203
  101. data/spec/hydra_attribute/memoizable_spec.rb +0 -95
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe HydraAttribute::Model::Cacheable do
4
+ before do
5
+ ::ActiveRecord::Base.connection.create_table(:custom_products) do |t|
6
+ t.string :name
7
+ t.float :price
8
+ t.integer :quantity
9
+ t.timestamps
10
+ end
11
+ Object.const_set('CustomProduct', Class.new)
12
+ CustomProduct.send(:include, HydraAttribute::Model::Validations) # dependency
13
+ CustomProduct.send(:include, HydraAttribute::Model::Persistence) # dependency
14
+ CustomProduct.send(:include, HydraAttribute::Model::IdentityMap) # dependency
15
+ CustomProduct.send(:include, HydraAttribute::Model::Cacheable)
16
+ end
17
+
18
+ after do
19
+ ::ActiveRecord::Base.connection.drop_table(:custom_products)
20
+ Object.send(:remove_const, 'CustomProduct')
21
+ end
22
+
23
+ describe '.all' do
24
+ it 'should find all models and store them into the cache' do
25
+ q1 = %q[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('one', 2.5, 5, '2012-12-12', '2012-12-12')]
26
+ q2 = %q[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('two', 3.5, 6, '2012-12-12', '2012-12-12')]
27
+
28
+ ActiveRecord::Base.connection.execute(q1)
29
+ ActiveRecord::Base.connection.execute(q2)
30
+
31
+ all = CustomProduct.all
32
+ all.should have(2).records
33
+
34
+ all.first.name.should == 'one'
35
+ all.last.name.should == 'two'
36
+
37
+ CustomProduct.identity_map[:all].should == all
38
+ end
39
+ end
40
+
41
+ describe '.find' do
42
+ it 'should load all records store them to the cache' do
43
+ q1 = %q[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('one', 2.5, 5, '2012-12-12', '2012-12-12')]
44
+ q2 = %q[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('two', 3.5, 6, '2012-12-12', '2012-12-12')]
45
+
46
+ id1 = ActiveRecord::Base.connection.insert(q1)
47
+ id2 = ActiveRecord::Base.connection.insert(q2)
48
+
49
+ CustomProduct.identity_map[:all].should be_nil
50
+ record = CustomProduct.find(id1)
51
+ record.name.should == 'one'
52
+
53
+ CustomProduct.identity_map[:all].should have(2).records
54
+ CustomProduct.identity_map[:all][0].id.should be(id1.to_i)
55
+ CustomProduct.identity_map[:all][1].id.should be(id2.to_i)
56
+
57
+ CustomProduct.nested_identity_map(:model)[id1.to_i].id.should be(id1.to_i)
58
+ CustomProduct.nested_identity_map(:model)[id2.to_i].id.should be(id2.to_i)
59
+ end
60
+
61
+ it 'should raise an error if cannot find the record' do
62
+ lambda do
63
+ CustomProduct.find(1)
64
+ end.should raise_error(HydraAttribute::RecordNotFound, "Couldn't find CustomProduct with id=1")
65
+ end
66
+ end
67
+
68
+ describe '#initialize' do
69
+ it 'should store model into the cache if it has an ID' do
70
+ product1 = CustomProduct.new(id: 1)
71
+ product2 = CustomProduct.new(id: 2)
72
+ CustomProduct.nested_identity_map(:model)[1].should be(product1)
73
+ CustomProduct.nested_identity_map(:model)[2].should be(product2)
74
+ end
75
+
76
+ it 'should not store model into the cache if it has not an ID' do
77
+ CustomProduct.new
78
+ CustomProduct.nested_identity_map(:model).should be_empty
79
+ end
80
+
81
+ it 'should not add model to the :all cache key' do
82
+ CustomProduct.new(id: 1)
83
+ CustomProduct.identity_map[:all].should be_nil
84
+ end
85
+ end
86
+
87
+ describe '#create' do
88
+ it 'should add model to the cache if it was a new model' do
89
+ product = CustomProduct.new
90
+ product.save
91
+ CustomProduct.nested_identity_map(:model)[product.id].should be(product)
92
+ end
93
+
94
+ it 'should not add model to the :all cache key if all models were not loaded before' do
95
+ CustomProduct.new.save
96
+ CustomProduct.identity_map[:all].should be_nil
97
+ end
98
+
99
+ it 'should add model to the :all cache key if all models were were loaded before' do
100
+ CustomProduct.all
101
+ product = CustomProduct.new
102
+ product.save
103
+ CustomProduct.identity_map[:all].should include(product)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe HydraAttribute::Model::HasManyThrough::Relation do
4
+ def find_relation_object(attributes = {})
5
+ HydraAttribute::HydraAttributeSet.all.find do |relation|
6
+ attributes.all? do |attribute, value|
7
+ relation.send(attribute) == value
8
+ end
9
+ end
10
+ end
11
+
12
+ describe '#build' do
13
+ let(:hydra_set) { Product.hydra_sets.create(name: 'default') }
14
+ let(:hydra_attr) { Product.hydra_attributes.create(name: 'color', backend_type: 'string') }
15
+
16
+ it 'should copy specific attribute to relation object' do
17
+ attr = hydra_set.hydra_attributes.build(name: 'title', backend_type: 'string')
18
+ attr.entity_type.should == 'Product'
19
+
20
+ set = hydra_attr.hydra_sets.build(name: 'second')
21
+ set.entity_type.should == 'Product'
22
+ end
23
+
24
+ it 'should save relation object after saving related object' do
25
+ attr = hydra_set.hydra_attributes.build(name: 'title', backend_type: 'string')
26
+ attr.should_not be_persisted
27
+ hydra_set.save
28
+ attr.should be_persisted
29
+ end
30
+
31
+ it 'should link to objects via relation table after saving main object' do
32
+ attr = hydra_set.hydra_attributes.build(name: 'size', backend_type: 'integer')
33
+ hydra_set.save
34
+
35
+ relation = find_relation_object(hydra_set_id: hydra_set.id, hydra_attribute_id: attr.id)
36
+ relation.should be_persisted
37
+ end
38
+ end
39
+
40
+ describe '#create' do
41
+ describe 'main object is persisted' do
42
+ let(:hydra_attr) { Product.hydra_attributes.create(name: 'one', backend_type: 'string') }
43
+
44
+ it 'should link objects via relation table' do
45
+ set = hydra_attr.hydra_sets.create(name: 'default')
46
+ set.should be_persisted
47
+
48
+ relation = find_relation_object(hydra_set_id: set.id, hydra_attribute_id: hydra_attr.id)
49
+ relation.should be_persisted
50
+ end
51
+ end
52
+
53
+ describe 'main object is new object' do
54
+ let(:hydra_attr) { Product.hydra_attributes.build(name: 'one', backend_type: 'string') }
55
+
56
+ it 'should link objects via relation table after saving main object' do
57
+ set = hydra_attr.hydra_sets.create(name: 'default')
58
+ set.should be_persisted
59
+
60
+ relation = find_relation_object(hydra_set_id: set.id, hydra_attribute_id: hydra_attr.id)
61
+ relation.should be_nil
62
+
63
+ hydra_attr.save
64
+ relation = find_relation_object(hydra_set_id: set.id, hydra_attribute_id: hydra_attr.id)
65
+ relation.should be_persisted
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#<<' do
71
+ let(:build_method) { :create }
72
+ let(:hydra_set) { Product.hydra_sets.create(name: 'default') }
73
+ let(:hydra_attr) { Product.hydra_attributes.send(build_method, name: 'one', backend_type: 'string') }
74
+
75
+ before { hydra_attr.hydra_sets << hydra_set }
76
+
77
+ describe 'main object is persisted' do
78
+ it 'should add relation object to collection' do
79
+ hydra_attr.hydra_sets.should include(hydra_set)
80
+ end
81
+
82
+ it 'should link objects via relation table' do
83
+ relation = find_relation_object(hydra_set_id: hydra_set.id, hydra_attribute_id: hydra_attr.id)
84
+ relation.should be_persisted
85
+ end
86
+ end
87
+
88
+ describe 'main object is new object' do
89
+ let(:build_method) { :build }
90
+
91
+ it 'should add relation object to collection' do
92
+ hydra_attr.hydra_sets.should include(hydra_set)
93
+ end
94
+
95
+ it 'should link objects via relation table after saving main object' do
96
+ relation = find_relation_object(hydra_set_id: hydra_set.id, hydra_attribute_id: hydra_attr.id)
97
+ relation.should be_nil
98
+
99
+ hydra_attr.save
100
+ relation = find_relation_object(hydra_set_id: hydra_set.id, hydra_attribute_id: hydra_attr.id)
101
+ relation.should be_persisted
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '#destroy' do
107
+ let(:build_method) { :create }
108
+ let(:hydra_set) { Product.hydra_sets.send(build_method, name: 'default') }
109
+ let(:hydra_attr) { hydra_set.hydra_attributes.create(name: 'one', backend_type: 'string') }
110
+
111
+ before { hydra_set.hydra_attributes.destroy(hydra_attr) }
112
+
113
+ describe 'main object is persisted' do
114
+ it 'should remove object from collection' do
115
+ hydra_set.hydra_attributes.should_not include(hydra_attr)
116
+ end
117
+
118
+ it 'should unlink objects from relation table' do
119
+ relation = find_relation_object(hydra_set_id: hydra_set.id, hydra_attribute_id: hydra_attr.id)
120
+ relation.should be_nil
121
+ end
122
+ end
123
+
124
+ describe 'main object is new object' do
125
+ let(:build_method) { :build }
126
+
127
+ it 'should remove object from collection' do
128
+ hydra_set.hydra_attributes.should_not include(hydra_attr)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe HydraAttribute::Model::IdentityMap do
4
+ before do
5
+ Object.const_set(:ExampleClass, Class.new)
6
+ ExampleClass.send(:include, HydraAttribute::Model::IdentityMap)
7
+ end
8
+
9
+ after do
10
+ Object.send(:remove_const, :ExampleClass)
11
+ end
12
+
13
+ describe '.identity_map_cache_key' do
14
+ it 'should create cache key based on class name' do
15
+ ExampleClass.identity_map_cache_key.should be(:example_class)
16
+ end
17
+ end
18
+
19
+ describe '.identity_map' do
20
+ it 'should return identity map object' do
21
+ ExampleClass.identity_map.should be_a_kind_of(HydraAttribute::IdentityMap)
22
+ end
23
+
24
+ it 'should store identity map in global identity map object' do
25
+ identity_map = ExampleClass.identity_map
26
+ HydraAttribute.identity_map[:example_class].should be(identity_map)
27
+ end
28
+ end
29
+
30
+ describe '.cache' do
31
+ it 'should proxy method to identity map object' do
32
+ ExampleClass.cache(:a, 1)
33
+ ExampleClass.identity_map.cache(:a, 2).should be(1)
34
+
35
+ ExampleClass.cache(:b) { 2 }
36
+ ExampleClass.identity_map.cache(:b) { 3 }.should be(2)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe HydraAttribute::Model::Mediator do
4
+ before do
5
+ Object.const_set('M1', Class.new)
6
+ Object.const_set('M2', Class.new)
7
+ Object.const_set('M3', Class.new)
8
+
9
+ M1.send(:include, HydraAttribute::Model::Mediator)
10
+ M2.send(:include, HydraAttribute::Model::Mediator)
11
+ M3.send(:include, HydraAttribute::Model::Mediator)
12
+
13
+ M1.observe 'M2', create: :m2_created, destroy: :m2_destroyed
14
+ M1.observe 'M3', create: :m3_created, destroy: :m3_destroyed
15
+ M2.observe 'M3', create: :m3_created, destroy: :m3_destroyed
16
+ M3.observe 'M1', create: :m1_created, destroy: :m1_destroyed
17
+
18
+ def M1.logger=(logger) @logger = logger end
19
+ def M2.logger=(logger) @logger = logger end
20
+ def M3.logger=(logger) @logger = logger end
21
+
22
+ def M1.m2_created(object) @logger << "M1:m2_created:#{object.__id__}" end
23
+ def M1.m3_created(object) @logger << "M1:m3_created:#{object.__id__}" end
24
+ def M2.m3_created(object) @logger << "M2:m3_created:#{object.__id__}" end
25
+ def M3.m1_created(object) @logger << "M3:m1_created:#{object.__id__}" end
26
+
27
+ def M1.m2_destroyed(object) @logger << "M1:m2_destroyed:#{object.__id__}" end
28
+ def M1.m3_destroyed(object) @logger << "M1:m3_destroyed:#{object.__id__}" end
29
+ def M2.m3_destroyed(object) @logger << "M2:m3_destroyed:#{object.__id__}" end
30
+ def M3.m1_destroyed(object) @logger << "M3:m1_destroyed:#{object.__id__}" end
31
+ end
32
+
33
+ after do
34
+ HydraAttribute::Model::Mediator.subscriptions.delete('M1')
35
+ HydraAttribute::Model::Mediator.subscriptions.delete('M2')
36
+ HydraAttribute::Model::Mediator.subscriptions.delete('M3')
37
+
38
+ Object.send(:remove_const, 'M1')
39
+ Object.send(:remove_const, 'M2')
40
+ Object.send(:remove_const, 'M3')
41
+ end
42
+
43
+ it 'should notify subscribed listeners' do
44
+ M1.logger = M2.logger = M3.logger = logger = []
45
+
46
+ m1, m2, m3 = M1.new, M2.new, M3.new
47
+
48
+ [:create, :destroy, :update].each do |event|
49
+ m1.notify(event); m2.notify(event); m3.notify(event)
50
+ end
51
+
52
+ logger[0].should == "M3:m1_created:#{m1.__id__}"
53
+ logger[1].should == "M1:m2_created:#{m2.__id__}"
54
+ logger[2].should == "M1:m3_created:#{m3.__id__}"
55
+ logger[3].should == "M2:m3_created:#{m3.__id__}"
56
+ logger[4].should == "M3:m1_destroyed:#{m1.__id__}"
57
+ logger[5].should == "M1:m2_destroyed:#{m2.__id__}"
58
+ logger[6].should == "M1:m3_destroyed:#{m3.__id__}"
59
+ logger[7].should == "M2:m3_destroyed:#{m3.__id__}"
60
+ logger[8].should be_nil # no more listeners
61
+ end
62
+ end
@@ -0,0 +1,550 @@
1
+ require 'spec_helper'
2
+
3
+ describe HydraAttribute::Model::Persistence do
4
+ before(:all) do
5
+ ::ActiveRecord::Base.connection.create_table(:custom_products) do |t|
6
+ t.string :name
7
+ t.decimal :price, precision: 4, scale: 2
8
+ t.integer :quantity
9
+ t.timestamps
10
+ end
11
+ Object.const_set('CustomProduct', Class.new)
12
+ CustomProduct.send(:include, HydraAttribute::Model::Validations) # dependency
13
+ CustomProduct.send(:include, HydraAttribute::Model::Persistence)
14
+ end
15
+
16
+ after(:all) do
17
+ ::ActiveRecord::Base.connection.drop_table(:custom_products)
18
+ Object.send(:remove_const, 'CustomProduct')
19
+ end
20
+
21
+ describe '.define_attribute_methods' do
22
+ before do
23
+ ::ActiveRecord::Base.connection.create_table(:example_products) do |t|
24
+ t.string :title
25
+ t.float :price
26
+ t.integer :count
27
+ t.timestamps
28
+ end
29
+ Object.const_set('ExampleProduct', Class.new)
30
+ ExampleProduct.send(:include, HydraAttribute::Model::Persistence)
31
+ end
32
+
33
+ after do
34
+ ::ActiveRecord::Base.connection.drop_table(:example_products)
35
+ Object.send(:remove_const, 'ExampleProduct')
36
+ end
37
+
38
+ it 'should generate attribute setter and getter' do
39
+ methods = [:title, :title=, :price, :price=, :count, :count=]
40
+ methods.each do |method|
41
+ ExampleProduct.method_defined?(method).should be_false
42
+ end
43
+
44
+ ExampleProduct.define_attribute_methods
45
+
46
+ methods.each do |method|
47
+ ExampleProduct.method_defined?(method).should be_true
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '.connection' do
53
+ it 'should return database adapter object' do
54
+ CustomProduct.connection.should == ActiveRecord::Base.connection
55
+ end
56
+ end
57
+
58
+ describe '.table_name' do
59
+ it 'should determine table name based on class name' do
60
+ CustomProduct.table_name.should == 'custom_products'
61
+ end
62
+ end
63
+
64
+ describe '.arel_table' do
65
+ it 'should build arel table for class' do
66
+ arel_table = CustomProduct.arel_table
67
+ arel_table.should be_a_kind_of(Arel::Table)
68
+ arel_table.table_name.should == 'custom_products'
69
+ arel_table.engine.should == CustomProduct
70
+ end
71
+ end
72
+
73
+ describe '.columns' do
74
+ it 'should return collection of objects which represent table columns' do
75
+ CustomProduct.should have(6).columns
76
+ CustomProduct.columns.each do |column|
77
+ column.should be_a_kind_of(ActiveRecord::ConnectionAdapters::Column)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '.column' do
83
+ it 'should return object which represents table column' do
84
+ CustomProduct.column('name').should be_a_kind_of(ActiveRecord::ConnectionAdapters::Column)
85
+ end
86
+ end
87
+
88
+ describe '.column_names' do
89
+ it 'should return all column names in table' do
90
+ CustomProduct.column_names.should == %w[id name price quantity created_at updated_at]
91
+ end
92
+ end
93
+
94
+ describe '.all' do
95
+ it 'should return blank array if table is empty' do
96
+ CustomProduct.all.should == []
97
+ end
98
+
99
+ it 'should return array of models if table has records' do
100
+ q1 = %[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('one', 2.5, 5, '2012-12-12', '2012-12-12')]
101
+ q2 = %[INSERT INTO custom_products (name, price, quantity, created_at, updated_at) VALUES ('two', 3.5, 6, '2012-12-12', '2012-12-12')]
102
+
103
+ ActiveRecord::Base.connection.execute(q1)
104
+ ActiveRecord::Base.connection.execute(q2)
105
+
106
+ all = CustomProduct.all
107
+ all.should have(2).items
108
+
109
+ all[0].should be_a_kind_of(CustomProduct)
110
+ all[0].name.should == 'one'
111
+ all[0].price.should == 2.5
112
+ all[0].quantity.should == 5
113
+
114
+ all[1].should be_a_kind_of(CustomProduct)
115
+ all[1].name.should == 'two'
116
+ all[1].price.should == 3.5
117
+ all[1].quantity.should == 6
118
+ end
119
+ end
120
+
121
+ describe '.find' do
122
+ it 'should raise HydraAttribute::RecordNotFound if cannot find a record' do
123
+ lambda do
124
+ CustomProduct.find(1)
125
+ end.should raise_error(HydraAttribute::RecordNotFound, %q(Couldn't find CustomProduct with id=1))
126
+ end
127
+
128
+ it 'should return model if record exists' do
129
+ q1 = %[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (1, 'book', 2.2, 3, '2012-12-12', '2012-12-12')]
130
+ ActiveRecord::Base.connection.execute(q1)
131
+
132
+ model = CustomProduct.find(1)
133
+ model.should be_a_kind_of(CustomProduct)
134
+ model.id.should == 1
135
+ model.name.should == 'book'
136
+ model.price.should == 2.2
137
+ model.quantity.should == 3
138
+ end
139
+ end
140
+
141
+ describe '.where' do
142
+ describe 'table is blank' do
143
+ it 'should return blank array' do
144
+ CustomProduct.where.should be_blank
145
+ end
146
+ end
147
+
148
+ describe 'table has records' do
149
+ before(:each) do
150
+ q1 = %[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (1, 'one', 1.1, 2, '2012-12-12', '2012-12-12')]
151
+ q2 = %[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (2, 'two', 2.2, 2, '2012-12-12', '2012-12-12')]
152
+ q3 = %[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (3, 'three', 3.3, 4, '2012-12-12', '2012-12-12')]
153
+
154
+ ActiveRecord::Base.connection.execute(q1)
155
+ ActiveRecord::Base.connection.execute(q2)
156
+ ActiveRecord::Base.connection.execute(q3)
157
+ end
158
+
159
+ it 'should return blank array if records do not match condition' do
160
+ CustomProduct.where(id: 4).should be_blank
161
+ end
162
+
163
+ it 'should select records which match condition' do
164
+ records = CustomProduct.where(quantity: 2, price: 2.2)
165
+ records.should have(1).item
166
+ records[0].id.should == 2
167
+ records[0].name.should == 'two'
168
+ records[0].price.should == 2.2
169
+ records[0].quantity.should == 2
170
+ end
171
+
172
+ it 'should select records by collection of values' do
173
+ records = CustomProduct.where(id: [1, 2])
174
+ records.should have(2).items
175
+
176
+ records[0].id.should == 1
177
+ records[0].name.should == 'one'
178
+ records[0].price.should == 1.1
179
+ records[0].quantity.should == 2
180
+
181
+ records[1].id.should == 2
182
+ records[1].name.should == 'two'
183
+ records[1].price.should == 2.2
184
+ records[1].quantity.should == 2
185
+ end
186
+
187
+ it 'should select certain columns' do
188
+ records = CustomProduct.where({quantity: 2}, %w[id name])
189
+ records.should have(2).items
190
+
191
+ records[0].id.should == 1
192
+ records[0].name.should == 'one'
193
+ records[0].price.should == nil
194
+ records[0].quantity.should == nil
195
+
196
+ records[1].id.should == 2
197
+ records[1].name.should == 'two'
198
+ records[1].price.should == nil
199
+ records[1].quantity.should == nil
200
+ end
201
+
202
+ it 'should limit records' do
203
+ records = CustomProduct.where({}, 'id', 2)
204
+ records.should have(2).times
205
+ records[0].id.should == 1
206
+ end
207
+
208
+ it 'should skip records' do
209
+ records = CustomProduct.where({}, 'id', nil, 1)
210
+ records.should have(2).times
211
+ records[0].id.should == 2
212
+ end
213
+ end
214
+ end
215
+
216
+ describe '.where_not' do
217
+ describe 'table is blank' do
218
+ it 'should return blank array' do
219
+ CustomProduct.where_not.should be_blank
220
+ end
221
+ end
222
+
223
+ describe 'table has records' do
224
+ before(:each) do
225
+ q1 = %q[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (1, 'one', 1.1, 2, '2012-12-12', '2012-12-12')]
226
+ q2 = %q[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (2, 'two', 2.2, 2, '2012-12-12', '2012-12-12')]
227
+ q3 = %q[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (3, 'three', 3.3, 4, '2012-12-12', '2012-12-12')]
228
+
229
+ ActiveRecord::Base.connection.execute(q1)
230
+ ActiveRecord::Base.connection.execute(q2)
231
+ ActiveRecord::Base.connection.execute(q3)
232
+ end
233
+
234
+ it 'should return models which does not match condition' do
235
+ records = CustomProduct.where_not(id: 1)
236
+ records.should have(2).items
237
+
238
+ records[0].id.should == 2
239
+ records[1].id.should == 3
240
+ end
241
+
242
+ it 'should accept array of values in query' do
243
+ records = CustomProduct.where_not(id: [1, 3])
244
+ records.should have(1).items
245
+
246
+ records[0].id.should == 2
247
+ end
248
+ end
249
+ end
250
+
251
+ describe '.create' do
252
+ it 'should create record and return ID of this record' do
253
+ model = CustomProduct.create(name: 'apple', price: 2.50, quantity: 5)
254
+ result = ActiveRecord::Base.connection.select_one(%[SELECT * FROM custom_products WHERE id=#{model.id}])
255
+
256
+ result['name'].should == 'apple'
257
+ result['price'].to_f.should == 2.50
258
+ result['quantity'].to_i.should == 5
259
+ end
260
+ end
261
+
262
+ describe '.update' do
263
+ it 'should update record by its ID' do
264
+ q = %[INSERT INTO custom_products (id, name, price, quantity, created_at, updated_at) VALUES (1, 'one', 1.1, 2, '2012-12-12', '2012-12-12')]
265
+ ActiveRecord::Base.connection.execute(q)
266
+
267
+ CustomProduct.update(1, name: 'book', price: 2.5)
268
+
269
+ result = ActiveRecord::Base.connection.select_one(%[SELECT * FROM custom_products WHERE id=1])
270
+ result['id'].to_i.should == 1
271
+ result['name'].should == 'book'
272
+ result['price'].to_f.should == 2.5
273
+ result['quantity'].to_i.should == 2
274
+ end
275
+ end
276
+
277
+ describe '.destroy' do
278
+ let!(:attr1) { Product.hydra_attributes.create(name: 'a1', backend_type: 'string') }
279
+ let!(:attr2) { Product.hydra_attributes.create(name: 'a2', backend_type: 'string') }
280
+
281
+ it 'should destroy model by ID' do
282
+ lambda { HydraAttribute::HydraAttribute.find(attr1.id) }.should_not raise_error(HydraAttribute::RecordNotFound)
283
+ lambda { HydraAttribute::HydraAttribute.find(attr2.id) }.should_not raise_error(HydraAttribute::RecordNotFound)
284
+
285
+ HydraAttribute::HydraAttribute.destroy(attr1.id)
286
+
287
+ lambda { HydraAttribute::HydraAttribute.find(attr1.id) }.should raise_error(HydraAttribute::RecordNotFound)
288
+ lambda { HydraAttribute::HydraAttribute.find(attr2.id) }.should_not raise_error(HydraAttribute::RecordNotFound)
289
+
290
+ HydraAttribute::HydraAttribute.destroy(attr2.id)
291
+
292
+ lambda { HydraAttribute::HydraAttribute.find(attr1.id) }.should raise_error(HydraAttribute::RecordNotFound)
293
+ lambda { HydraAttribute::HydraAttribute.find(attr2.id) }.should raise_error(HydraAttribute::RecordNotFound)
294
+ end
295
+ end
296
+
297
+ describe '.destroy_all' do
298
+ let!(:attr1) { HydraAttribute::HydraAttribute.create(entity_type: 'Product', name: 'a1', backend_type: 'string') }
299
+ let!(:attr2) { HydraAttribute::HydraAttribute.create(entity_type: 'Product', name: 'a2', backend_type: 'string') }
300
+ let!(:attr3) { HydraAttribute::HydraAttribute.create(entity_type: 'Product', name: 'a3', backend_type: 'string') }
301
+
302
+ it 'should delete all models' do
303
+ HydraAttribute::HydraAttribute.count.should be(3)
304
+ HydraAttribute::HydraAttribute.destroy_all
305
+ HydraAttribute::HydraAttribute.count.should be(0)
306
+ end
307
+
308
+ it 'should return result for every deleted object' do
309
+ result = HydraAttribute::HydraAttribute.destroy_all
310
+ result.should == {attr1.id => true, attr2.id => true, attr3.id => true}
311
+ end
312
+ end
313
+
314
+ describe '#attributes' do
315
+ it 'should return all attributes' do
316
+ product = CustomProduct.new(name: 'a', price: 2)
317
+ product.attributes.should == {id: nil, name: 'a', price: 2, quantity: nil, updated_at: nil, created_at: nil}
318
+ end
319
+ end
320
+
321
+ describe '#persisted?' do
322
+ it 'should return true if ID exists' do
323
+ product = CustomProduct.new(id: 1)
324
+ product.should be_persisted
325
+ end
326
+
327
+ it 'should return false if ID does not exist' do
328
+ product = CustomProduct.new
329
+ product.should_not be_persisted
330
+ end
331
+
332
+ it 'should return false if ID exists but it is nil' do
333
+ product = CustomProduct.new(id: nil)
334
+ product.should_not be_persisted
335
+ end
336
+
337
+ it 'should return false if record is destroyed' do
338
+ product = CustomProduct.create(name: 'book')
339
+ product.should be_persisted
340
+
341
+ product.destroy
342
+ product.should_not be_persisted
343
+ end
344
+ end
345
+
346
+ describe '#destroyed?' do
347
+ it 'should return false for new object' do
348
+ product = CustomProduct.new
349
+ product.should_not be_destroyed
350
+ end
351
+
352
+ it 'should return false for created object' do
353
+ product = CustomProduct.create(name: 'book')
354
+ product.should_not be_destroyed
355
+ end
356
+
357
+ it 'should return true if object was destroyed' do
358
+ product = CustomProduct.create(name: 'book')
359
+ product.destroy
360
+ product.should be_destroyed
361
+ end
362
+ end
363
+
364
+ describe '#save' do
365
+ describe 'create' do
366
+ describe 'when save is succeed' do
367
+ it 'should create blank record' do
368
+ product = CustomProduct.new
369
+ product.save
370
+ product.id.should_not be_nil
371
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM custom_products WHERE id=#{product.id}").to_i.should be(1)
372
+ end
373
+
374
+ it 'should create record with several fields' do
375
+ product = CustomProduct.new(name: 'my name', price: 2.50, quantity: 4)
376
+ product.save
377
+ product.id.should_not be_nil
378
+
379
+ results = ActiveRecord::Base.connection.select_one("SELECT * FROM custom_products WHERE id=#{product.id}")
380
+ results['id'].to_i.should == product.id
381
+ results['name'].should == 'my name'
382
+ results['price'].to_f.should == 2.50
383
+ results['quantity'].to_i.should == 4
384
+ end
385
+ end
386
+
387
+ describe 'when save is failed' do
388
+ before do
389
+ CustomProduct.send(:include, HydraAttribute::Model::Mediator)
390
+ CustomProduct.send(:include, HydraAttribute::Model::Notifiable)
391
+
392
+ class CustomObserverClass
393
+ include HydraAttribute::Model::Mediator
394
+ observe 'CustomProduct', after_create: :after_create
395
+ def self.after_create(*) raise Exception, 'Testing rollback' end
396
+ end
397
+ end
398
+
399
+ after do
400
+ Object.send(:remove_const, 'CustomObserverClass')
401
+ end
402
+
403
+ it 'should not commit insert query if error was raised during saving' do
404
+ lambda { CustomProduct.new.save }.should raise_error(Exception, 'Testing rollback')
405
+ CustomProduct.connection.select_value('SELECT COUNT(*) FROM custom_products').to_i.should be(0)
406
+ end
407
+ end
408
+ end
409
+
410
+ describe 'update' do
411
+ describe 'when save is succeed' do
412
+ it 'should update record if id exists' do
413
+ ActiveRecord::Base.connection.insert(%q[INSERT INTO custom_products(id, name, price, quantity, created_at, updated_at) VALUES (1, 'book', 35.5, 6, '2012-12-12', '2012-12-12')])
414
+
415
+ product = CustomProduct.new(id: 1, name: 'book 2', price: 45.7, quantity: 10)
416
+ product.save
417
+
418
+ results = ActiveRecord::Base.connection.select_one("SELECT * FROM custom_products WHERE id=#{product.id}")
419
+ results['id'].to_i.should == product.id
420
+ results['name'].should == 'book 2'
421
+ results['price'].to_f.should == 45.7
422
+ results['quantity'].to_i.should == 10
423
+ end
424
+ end
425
+
426
+ describe 'when save is failed' do
427
+ before do
428
+ CustomProduct.send(:include, HydraAttribute::Model::Mediator)
429
+ CustomProduct.send(:include, HydraAttribute::Model::Notifiable)
430
+
431
+ class CustomObserverClass
432
+ include HydraAttribute::Model::Mediator
433
+ observe 'CustomProduct', after_update: :after_update
434
+ def self.after_update(*) raise Exception, 'Testing rollback' end
435
+ end
436
+ end
437
+
438
+ after do
439
+ Object.send(:remove_const, 'CustomObserverClass')
440
+ end
441
+
442
+ it 'should not update record if error was raised during saving' do
443
+ ActiveRecord::Base.connection.insert(%q[INSERT INTO custom_products(id, name, price, quantity, created_at, updated_at) VALUES (1, 'book', 35.5, 6, '2012-12-12', '2012-12-12')])
444
+ lambda { CustomProduct.new(id: 1, name: 'ball').save }.should raise_error(Exception, 'Testing rollback')
445
+ CustomProduct.connection.select_value("SELECT name FROM custom_products WHERE id=1").should == 'book'
446
+ end
447
+ end
448
+ end
449
+ end
450
+
451
+ describe '#destroy' do
452
+ describe 'when destroy is succeed' do
453
+ it 'should delete record from database' do
454
+ ActiveRecord::Base.connection.insert(%q[INSERT INTO custom_products(id, created_at, updated_at) VALUES (1, '2012-12-12', '2012-12-12')])
455
+ product = CustomProduct.new(id: 1)
456
+ product.destroy
457
+ ActiveRecord::Base.connection.select_value('SELECT COUNT(*) FROM custom_products WHERE id=1').to_i.should be(0)
458
+ end
459
+ end
460
+
461
+ describe 'when destroy is failed' do
462
+ before do
463
+ CustomProduct.send(:include, HydraAttribute::Model::Mediator)
464
+ CustomProduct.send(:include, HydraAttribute::Model::Notifiable)
465
+
466
+ class CustomProductObserverClass
467
+ include HydraAttribute::Model::Mediator
468
+ observe 'CustomProduct', after_destroy: :after_destroy
469
+ def self.after_destroy(*) raise Exception, 'Testing rollback' end
470
+ end
471
+ end
472
+
473
+ after do
474
+ Object.send(:remove_const, 'CustomProductObserverClass')
475
+ end
476
+
477
+ it 'should not commit delete query if error was raised during destroying' do
478
+ ActiveRecord::Base.connection.insert(%q[INSERT INTO custom_products(id, created_at, updated_at) VALUES (1, '2012-12-12', '2012-12-12')])
479
+ product = CustomProduct.new(id: 1)
480
+ lambda { product.destroy }.should raise_error(Exception, 'Testing rollback')
481
+ ActiveRecord::Base.connection.select_value('SELECT COUNT(*) FROM custom_products WHERE id=1').to_i.should be(1)
482
+ end
483
+ end
484
+ end
485
+
486
+ describe 'auto generated attribute methods' do
487
+ before do
488
+ ::ActiveRecord::Base.connection.create_table(:example_products) do |t|
489
+ t.string :title
490
+ t.float :price
491
+ t.integer :count
492
+ t.timestamps
493
+ end
494
+ Object.const_set('ExampleProduct', Class.new)
495
+ ExampleProduct.send(:include, HydraAttribute::Model)
496
+ end
497
+
498
+ after do
499
+ ::ActiveRecord::Base.connection.drop_table(:example_products)
500
+ Object.send(:remove_const, 'ExampleProduct')
501
+ end
502
+
503
+ it 'should respond to attribute methods' do
504
+ ExampleProduct.new.should respond_to(:title)
505
+ ExampleProduct.new.should respond_to(:title=)
506
+ ExampleProduct.new.should respond_to(:title?)
507
+ ExampleProduct.new.should respond_to(:price)
508
+ ExampleProduct.new.should respond_to(:price=)
509
+ ExampleProduct.new.should respond_to(:price?)
510
+ ExampleProduct.new.should respond_to(:count)
511
+ ExampleProduct.new.should respond_to(:count=)
512
+ ExampleProduct.new.should respond_to(:count?)
513
+ end
514
+
515
+ it 'should get values from attributes' do
516
+ product = ExampleProduct.new(title: 'a', price: 2, count: 3)
517
+ product.title.should == 'a'
518
+ product.price.should == 2
519
+ product.count.should == 3
520
+ end
521
+
522
+ it 'should set values to attributes' do
523
+ product = ExampleProduct.new
524
+ product.title = 'b'
525
+ product.price = 3
526
+ product.count = 4
527
+
528
+ product.title.should == 'b'
529
+ product.price.should == 3
530
+ product.count.should == 4
531
+ end
532
+
533
+ it 'should type cast value before set it' do
534
+ product = ExampleProduct.new
535
+ product.count = '1'
536
+ product.count.should be(1)
537
+ end
538
+
539
+ it 'should validate values' do
540
+ product = ExampleProduct.new
541
+ product.title?.should be_false
542
+
543
+ product.title = ''
544
+ product.title?.should be_false
545
+
546
+ product.title = 'a'
547
+ product.title?.should be_true
548
+ end
549
+ end
550
+ end