opium 1.0.0.beta

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +24 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +71 -0
  9. data/Rakefile +10 -0
  10. data/lib/generators/opium/config_generator.rb +15 -0
  11. data/lib/generators/opium/model_generator.rb +33 -0
  12. data/lib/generators/opium/templates/config.yml +27 -0
  13. data/lib/generators/opium/templates/model.rb +10 -0
  14. data/lib/opium/config.rb +44 -0
  15. data/lib/opium/extensions/array.rb +10 -0
  16. data/lib/opium/extensions/boolean.rb +13 -0
  17. data/lib/opium/extensions/date.rb +18 -0
  18. data/lib/opium/extensions/date_time.rb +18 -0
  19. data/lib/opium/extensions/false_class.rb +7 -0
  20. data/lib/opium/extensions/float.rb +13 -0
  21. data/lib/opium/extensions/geo_point.rb +37 -0
  22. data/lib/opium/extensions/hash.rb +43 -0
  23. data/lib/opium/extensions/integer.rb +13 -0
  24. data/lib/opium/extensions/numeric.rb +7 -0
  25. data/lib/opium/extensions/object.rb +15 -0
  26. data/lib/opium/extensions/pointer.rb +20 -0
  27. data/lib/opium/extensions/regexp.rb +12 -0
  28. data/lib/opium/extensions/string.rb +20 -0
  29. data/lib/opium/extensions/time.rb +19 -0
  30. data/lib/opium/extensions/true_class.rb +7 -0
  31. data/lib/opium/extensions.rb +16 -0
  32. data/lib/opium/model/attributable.rb +37 -0
  33. data/lib/opium/model/callbacks.rb +38 -0
  34. data/lib/opium/model/connectable.rb +155 -0
  35. data/lib/opium/model/criteria.rb +123 -0
  36. data/lib/opium/model/dirty.rb +35 -0
  37. data/lib/opium/model/field.rb +31 -0
  38. data/lib/opium/model/fieldable.rb +57 -0
  39. data/lib/opium/model/findable.rb +20 -0
  40. data/lib/opium/model/kaminari/queryable.rb +46 -0
  41. data/lib/opium/model/kaminari/scopable.rb +15 -0
  42. data/lib/opium/model/kaminari.rb +4 -0
  43. data/lib/opium/model/persistable.rb +153 -0
  44. data/lib/opium/model/queryable.rb +150 -0
  45. data/lib/opium/model/scopable.rb +58 -0
  46. data/lib/opium/model/serialization.rb +13 -0
  47. data/lib/opium/model.rb +47 -0
  48. data/lib/opium/railtie.rb +14 -0
  49. data/lib/opium/user.rb +44 -0
  50. data/lib/opium/version.rb +3 -0
  51. data/lib/opium.rb +9 -0
  52. data/opium.gemspec +40 -0
  53. data/spec/opium/config/opium.yml +5 -0
  54. data/spec/opium/config_spec.rb +56 -0
  55. data/spec/opium/extensions/array_spec.rb +34 -0
  56. data/spec/opium/extensions/boolean_spec.rb +28 -0
  57. data/spec/opium/extensions/date_spec.rb +55 -0
  58. data/spec/opium/extensions/date_time_spec.rb +55 -0
  59. data/spec/opium/extensions/float_spec.rb +42 -0
  60. data/spec/opium/extensions/geo_point_spec.rb +55 -0
  61. data/spec/opium/extensions/hash_spec.rb +76 -0
  62. data/spec/opium/extensions/integer_spec.rb +42 -0
  63. data/spec/opium/extensions/object_spec.rb +24 -0
  64. data/spec/opium/extensions/pointer_spec.rb +28 -0
  65. data/spec/opium/extensions/regexp_spec.rb +23 -0
  66. data/spec/opium/extensions/string_spec.rb +65 -0
  67. data/spec/opium/extensions/time_spec.rb +55 -0
  68. data/spec/opium/model/attributable_spec.rb +45 -0
  69. data/spec/opium/model/callbacks_spec.rb +59 -0
  70. data/spec/opium/model/connectable_spec.rb +218 -0
  71. data/spec/opium/model/criteria_spec.rb +285 -0
  72. data/spec/opium/model/dirty_spec.rb +39 -0
  73. data/spec/opium/model/fieldable_spec.rb +133 -0
  74. data/spec/opium/model/findable_spec.rb +57 -0
  75. data/spec/opium/model/kaminari/queryable_spec.rb +22 -0
  76. data/spec/opium/model/kaminari/scopable_spec.rb +20 -0
  77. data/spec/opium/model/kaminari_spec.rb +104 -0
  78. data/spec/opium/model/persistable_spec.rb +367 -0
  79. data/spec/opium/model/queryable_spec.rb +338 -0
  80. data/spec/opium/model/scopable_spec.rb +115 -0
  81. data/spec/opium/model/serialization_spec.rb +51 -0
  82. data/spec/opium/model_spec.rb +49 -0
  83. data/spec/opium/user_spec.rb +195 -0
  84. data/spec/opium_spec.rb +5 -0
  85. data/spec/spec_helper.rb +25 -0
  86. metadata +400 -0
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::Model::Findable do
4
+ before do
5
+ stub_const( 'Model', Class.new do
6
+ include Opium::Model::Findable
7
+ end )
8
+ stub_const( 'Game', Class.new do
9
+ include Opium::Model
10
+ field :title, type: String
11
+ field :release_price, type: Float
12
+ end )
13
+ end
14
+
15
+ describe 'the module' do
16
+ subject { Model }
17
+
18
+ it { is_expected.to respond_to( :find ).with(1).argument }
19
+ it { is_expected.to respond_to( :first, :each, :each_with_index ) }
20
+ it { is_expected.to respond_to( :map ) }
21
+ end
22
+
23
+ describe '.find' do
24
+ before do
25
+ stub_request( :get, 'https://api.parse.com/1/classes/Game/abcd1234' ).to_return(
26
+ body: { objectId: 'abcd1234', title: 'Skyrim', releasePrice: 59.99, createdAt: '2011-11-11T12:00:00Z', updatedAt: '2014-11-11T15:13:47Z' }.to_json,
27
+ status: 200,
28
+ headers: { content_type: 'application/json' }
29
+ )
30
+
31
+ stub_request( :get, 'https://api.parse.com/1/classes/Game/deadbeef' ).to_return(
32
+ body: { code: 404, error: 'Could not locate a "Game" with id "deadbeef"' }.to_json,
33
+ status: 404,
34
+ headers: { content_type: 'application/json' }
35
+ )
36
+ end
37
+
38
+ let(:result) { Game.find( id ) }
39
+
40
+ context 'when a model exists' do
41
+ let(:id) { 'abcd1234' }
42
+
43
+ it { expect { result }.to_not raise_exception }
44
+ it { expect( result ).to be_an( Opium::Model ) }
45
+ it { expect( result ).to be_a( Game ) }
46
+ it 'has the proper model attributes' do
47
+ expect( result.attributes ).to include( title: 'Skyrim', release_price: 59.99 )
48
+ end
49
+ end
50
+
51
+ context 'when a model does not exist' do
52
+ let(:id) { 'deadbeef' }
53
+
54
+ it { expect { result }.to raise_exception }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?( Kaminari )
4
+ describe Opium::Model::Queryable do
5
+ describe 'when included in a class' do
6
+ before do
7
+ stub_const( 'Query', Class.new { include Opium::Model::Queryable } )
8
+ end
9
+
10
+ subject { Query }
11
+
12
+ it { should <= Opium::Model::Queryable }
13
+ it { Opium::Model::Queryable::ClassMethods <= ::Kaminari::PageScopeMethods }
14
+
15
+ # Really, this just is a sanity check to verify that some of the kaminari methods
16
+ # successfully were added.
17
+ it { should respond_to( :total_pages, :current_page, :max_pages, :entry_name ) }
18
+ it { should respond_to( Kaminari.config.page_method_name ).with(1).argument }
19
+ it { should respond_to( :limit, :offset ) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?( Kaminari )
4
+ describe Opium::Model::Scopable do
5
+ describe 'when included in a class' do
6
+ before do
7
+ stub_const( 'Scoped', Class.new { include Opium::Model::Scopable } )
8
+ end
9
+
10
+ subject { Scoped }
11
+
12
+ it { should <= Opium::Model::Scopable }
13
+ it { should <= ::Kaminari::ConfigurationMethods }
14
+
15
+ # Really, this just is a sanity check to verify that some of the kaminari methods
16
+ # successfully were added.
17
+ it { should respond_to( :max_per_page, :max_pages ) }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?( Kaminari )
4
+ describe 'when Kaminari is present' do
5
+ before do
6
+ stub_const( 'Game', Class.new do
7
+ include Opium::Model
8
+ field :title, type: String
9
+ field :price, type: Float
10
+ end )
11
+ stub_request(:get, "https://api.parse.com/1/classes/Game?count=1&limit=10&skip=0").
12
+ with(:headers => {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Rest-Api-Key'=>'PARSE_API_KEY'}).
13
+ to_return(
14
+ status: 200,
15
+ body: {
16
+ count: 100,
17
+ results: []
18
+ }.to_json,
19
+ headers: { content_type: 'application/json' }
20
+ )
21
+ end
22
+
23
+ describe Opium::Model do
24
+ subject { Game }
25
+
26
+ it { is_expected.to respond_to(:page, :per).with(1).argument }
27
+
28
+ describe '.page' do
29
+ let( :query ) { subject.page( 5 ) }
30
+ it { expect { query }.to_not raise_exception }
31
+ it { expect( query ).to be_an( Opium::Model::Criteria ) }
32
+ it { expect( query.offset_value ).to eq 100 }
33
+ it { expect( query ).to be_cached }
34
+ end
35
+
36
+ describe '.per' do
37
+ let( :query ) { subject.per( 20 ) }
38
+ it { expect { query }.to_not raise_exception }
39
+ it { expect( query.limit_value ).to eq 20 }
40
+ it { expect( query ).to be_cached }
41
+ end
42
+
43
+ describe '.limit_value' do
44
+ let( :query ) { subject.limit_value }
45
+ it { expect { query }.to_not raise_exception }
46
+ it { expect( query ).to eq ::Kaminari.config.default_per_page }
47
+ end
48
+
49
+ describe '.offset_value' do
50
+ let( :query ) { subject.offset_value }
51
+ it { expect { query }.to_not raise_exception }
52
+ it { expect( query ).to eq 0 }
53
+ end
54
+
55
+ context 'when .page is called after .per' do
56
+ let( :query ) { subject.per( 10 ).page( 2 ) }
57
+ it { expect { query }.to_not raise_exception }
58
+
59
+ it 'sets the offset_value correctly' do
60
+ expect( query.offset_value ).to eq 10
61
+ end
62
+
63
+ it 'sets the limit_value correctly' do
64
+ expect( query.limit_value ).to eq 10
65
+ end
66
+ end
67
+ end
68
+
69
+ describe Opium::Model::Criteria do
70
+ subject { Game.criteria }
71
+
72
+ it { is_expected.to be_a( ::Kaminari::PageScopeMethods ) }
73
+ it { is_expected.to respond_to(:page, :per).with(1).argument }
74
+ it { is_expected.to respond_to(:limit_value, :offset_value) }
75
+
76
+ describe '#page' do
77
+ let( :query ) { subject.page( 0 ) }
78
+ it { expect { query }.to_not raise_exception }
79
+ it { expect( query.offset_value ).to eq 0 }
80
+ end
81
+
82
+ describe '#per' do
83
+ let( :query ) { subject.page( 2 ).per( 10 ) }
84
+ it { expect { query }.to_not raise_exception }
85
+ it { expect( query.max_per_page ).to be_nil }
86
+ it { expect( query.constraints[:limit] ).to eq 10 }
87
+ it { expect( query.constraints[:skip] ).to eq 10 }
88
+ it { expect( query.limit_value ).to eq 10 }
89
+ it { expect( query.offset_value ).to eq 10 }
90
+ end
91
+
92
+ describe '#total_pages' do
93
+ let( :query ) { subject.page( 0 ).per( 10 ) }
94
+ it { expect( query.offset_value ).to be_truthy }
95
+ it { expect { query.total_pages }.to_not raise_exception }
96
+ it { expect( query.total_pages ).to eq 10 }
97
+ end
98
+
99
+ describe '#entry_name' do
100
+ it { expect( subject.entry_name ).to eq Game.model_name.human.downcase }
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,367 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Opium::Model::Persistable do
4
+ let( :model ) { Class.new { include Opium::Model::Persistable } }
5
+
6
+ describe 'in a model' do
7
+ subject { model }
8
+
9
+ it { should respond_to( :destroy_all ).with(1).argument }
10
+ it { should respond_to( :delete_all ).with(1).argument }
11
+ it { should respond_to( :create, :create! ).with(1).argument }
12
+ it { should respond_to( :add_header_to ).with(4).arguments }
13
+ it { should respond_to( :added_headers ) }
14
+ it { should respond_to( :get_header_for ).with(2).arguments }
15
+
16
+ describe ':added_headers' do
17
+ it { subject.added_headers.should be_a( Hash ) }
18
+ end
19
+
20
+ describe ':add_header_to' do
21
+ after { subject.added_headers.clear }
22
+
23
+ it { expect { subject.add_header_to :put, :x_header, 42 }.to_not raise_exception }
24
+ it { subject.add_header_to(:delete, :x_header, 37, only: :delete).should be_nil }
25
+
26
+ it 'should add header information to :added_headers' do
27
+ subject.add_header_to( :put, :x_header, 42, except: :update )
28
+ subject.added_headers.should have_key(:put)
29
+ subject.added_headers[:put].should include( :header, :value, :options )
30
+ end
31
+ end
32
+
33
+ describe ':get_header_for' do
34
+ after { subject.added_headers.clear }
35
+
36
+ it 'should return an empty hash if a method has no added headers' do
37
+ subject.get_header_for( :put, :update ).should == {}
38
+ end
39
+
40
+ it 'should return an empty hash if a context is not within the only list for a method' do
41
+ subject.add_header_to( :put, :x_header, 42, only: [:foo, :bar] )
42
+ subject.get_header_for( :put, :baz ).should == {}
43
+ end
44
+
45
+ it 'should return an empty hash if a context is within the except list for a method' do
46
+ subject.add_header_to( :put, :x_header, 42, except: [:foo, :bar] )
47
+ subject.get_header_for( :put, :foo ).should == {}
48
+ end
49
+
50
+ it 'should return a headers hash if context-free' do
51
+ subject.add_header_to( :put, :x_header, 42 )
52
+ subject.get_header_for( :put, :update ).should == { headers: { x_header: 42 } }
53
+ end
54
+
55
+ it 'should return a headers hash if a context is within the only list for a method' do
56
+ subject.add_header_to( :put, :x_header, 42, only: :update )
57
+ subject.get_header_for( :put, :update ).should == { headers: { x_header: 42 } }
58
+ end
59
+
60
+ it 'should return a headers hash if a context is not within the except list for a method' do
61
+ subject.add_header_to( :put, :x_header, 42, except: :foo )
62
+ subject.get_header_for( :put, :update ).should == { headers: { x_header: 42 } }
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'instance' do
68
+ subject { model.new }
69
+
70
+ it { should respond_to( :save ).with(1).argument }
71
+ it { should respond_to( :save! ).with(0).arguments }
72
+ it { should respond_to( :update_attributes, :update_attributes! ).with(1).argument }
73
+ it { should respond_to( :touch ) }
74
+ it { should respond_to( :destroy ) }
75
+ it { should respond_to( :delete ) }
76
+ it { should respond_to( :new_record?, :persisted? ) }
77
+ it { should respond_to( :pointer, :to_parse ) }
78
+ end
79
+
80
+ describe 'within a model' do
81
+ before do
82
+ stub_const( 'Game', Class.new do
83
+ include Opium::Model
84
+ field :title, type: String
85
+ field :released_on, type: Date
86
+ field :release_price, type: Float
87
+
88
+ validates :release_price, numericality: { greater_than: 0 }
89
+ end )
90
+ end
91
+
92
+ after do
93
+ Opium::Model::Criteria.models.clear
94
+ end
95
+
96
+ describe ':new_record?' do
97
+ subject { Game.new }
98
+
99
+ it 'should be true in a model without an id' do
100
+ subject.should be_a_new_record
101
+ end
102
+
103
+ it 'should be false in a model with an id' do
104
+ subject.attributes = { id: 'abcd1234' }
105
+ subject.should_not be_a_new_record
106
+ end
107
+ end
108
+
109
+ describe 'when changing the attributes of a model' do
110
+ subject { Game.new( id: 'abcd1234' ) }
111
+
112
+ it { expect { subject.attributes = { title: 'Skyrim' } }.to change( subject, :persisted? ).from(true).to(false) }
113
+ end
114
+
115
+ describe 'a new model can be created with' do
116
+ before do
117
+ stub_request( :post, 'https://api.parse.com/1/classes/Game' ).with(
118
+ body: { title: 'Skyrim', releasedOn: { '__type' => 'Date', 'iso' => '2011-11-11' }, releasePrice: 59.99 },
119
+ headers: { 'Content-Type' => 'application/json' }
120
+ ).to_return(
121
+ body: { objectId: 'abcd1234', createdAt: Time.now.to_s }.to_json,
122
+ status: 200,
123
+ headers: { 'Content-Type' => 'application/json', Location: 'https://api.parse.com/1/classes/Game/abcd1234' }
124
+ )
125
+ end
126
+
127
+ describe ':create' do
128
+ it 'which should return a persisted? model' do
129
+ result = Game.create( title: 'Skyrim', released_on: '2011-11-11', release_price: 59.99 )
130
+ result.should_not be_a_new_record
131
+ result.should be_persisted
132
+ end
133
+ end
134
+
135
+ describe ':create!' do
136
+ it 'which should return a persisted? model' do
137
+ result = Game.create( title: 'Skyrim', released_on: '2011-11-11', release_price: 59.99 )
138
+ result.should_not be_a_new_record
139
+ result.should be_persisted
140
+ end
141
+ end
142
+ end
143
+
144
+ describe 'attepting to' do
145
+ describe ':create an invalid model' do
146
+ it 'should not raise an exception' do
147
+ expect { Game.create( release_price: -10.00 ) }.to_not raise_exception
148
+ end
149
+
150
+ it 'should have errors' do
151
+ result = Game.create( release_price: -10.00 )
152
+ result.should_not be_persisted
153
+ result.errors.should_not be_empty
154
+ end
155
+ end
156
+
157
+ describe ':create! an invalid model' do
158
+ it 'should raise an exception' do
159
+ expect { Game.create!( release_price: -10.00 ) }.to raise_exception
160
+ end
161
+ end
162
+ end
163
+
164
+ describe 'when saving a new model' do
165
+ subject { Game.new( title: 'Skyrim', released_on: '2011-11-11', release_price: '59.99' ) }
166
+
167
+ before do
168
+ stub_request( :post, 'https://api.parse.com/1/classes/Game' ).with(
169
+ body: { title: 'Skyrim', releasedOn: { '__type' => 'Date', 'iso' => '2011-11-11' }, releasePrice: 59.99 },
170
+ headers: { 'Content-Type' => 'application/json' }
171
+ ).to_return(
172
+ body: { objectId: 'abcd1234', createdAt: Time.now.to_s }.to_json,
173
+ status: 200,
174
+ headers: { 'Content-Type' => 'application/json', Location: 'https://api.parse.com/1/classes/Game/abcd1234' }
175
+ )
176
+ end
177
+
178
+ it 'should have its object_id and created_at fields updated' do
179
+ subject.id.should be_nil
180
+ subject.created_at.should be_nil
181
+ subject.should be_a_new_record
182
+ subject.should_not be_persisted
183
+ subject.save.should == true
184
+ subject.should_not be_a_new_record
185
+ subject.should be_persisted
186
+ end
187
+ end
188
+
189
+ describe 'when saving an existing model' do
190
+ subject { Game.new( id: 'abcd1234', created_at: Time.now - 3600, title: 'Skyrim' ) }
191
+
192
+ before do
193
+ stub_request( :put, 'https://api.parse.com/1/classes/Game/abcd1234' ).with(
194
+ body: { releasedOn: { '__type' => 'Date', 'iso' => '2011-11-11' }, releasePrice: 59.99 },
195
+ headers: { content_type: 'application/json' }
196
+ ).to_return(
197
+ body: { updatedAt: Time.now.to_s }.to_json,
198
+ status: 200,
199
+ headers: { content_type: 'application/json', Location: 'https://api.parse.com/1/classes/Game/abcd1234' }
200
+ )
201
+ end
202
+
203
+ before :each do
204
+ subject.attributes = { released_on: '2011-11-11', release_price: 59.99 }
205
+ end
206
+
207
+ it 'should have its updated_at fields updated' do
208
+ subject.save.should == true
209
+ subject.should be_persisted
210
+ subject.updated_at.should_not be_nil
211
+ end
212
+
213
+ it ':save! should not raise an exception' do
214
+ subject.release_price.should_not be_nil
215
+ expect { subject.save! }.to_not raise_exception
216
+ end
217
+ end
218
+
219
+ describe 'when saving a model causes a ParseError' do
220
+ subject { Game.new( id: 'deadbeef', created_at: Time.now - 3600, title: 'Skyrim' ) }
221
+
222
+ before do
223
+ stub_request( :put, 'https://api.parse.com/1/classes/Game/deadbeef' ).with(
224
+ body: { releasePrice: 599.99 },
225
+ headers: { 'Content-Type' => 'application/json' }
226
+ ).to_return(
227
+ body: { code: 404, error: 'Could not locate a "Game" with id of "deadbeef".' }.to_json,
228
+ status: 404,
229
+ headers: { 'Content-Type' => 'application/json' }
230
+ )
231
+ end
232
+
233
+ before(:each) do
234
+ subject.release_price = 599.99
235
+ end
236
+
237
+ it { expect { subject.save }.to_not raise_exception }
238
+
239
+ it ':save should return false and have errors' do
240
+ subject.save.should == false
241
+ subject.errors.should_not be_empty
242
+ end
243
+
244
+ it { expect { subject.save! }.to raise_exception }
245
+ end
246
+
247
+ describe 'when saving a model with validates: false' do
248
+ subject { Game.new( title: 'Skyrim' ) }
249
+
250
+ it 'should not receive :valid?, but should receive :create' do
251
+ subject.should_not receive(:valid?)
252
+ subject.should receive(:create)
253
+ subject.save( validates: false )
254
+ end
255
+ end
256
+
257
+ describe 'when saving an invalid model' do
258
+ subject { Game.new( title: 'Skyrim', release_price: -10.99 ) }
259
+
260
+ it ':save should return false and have :errors' do
261
+ subject.save.should == false
262
+ subject.errors.should_not be_empty
263
+ end
264
+
265
+ it ':save! should raise an exception' do
266
+ expect { subject.save! }.to raise_exception
267
+ end
268
+ end
269
+
270
+ describe ':update_attributes' do
271
+ subject { Game.new( id: 'abcd1234', title: 'Skyrim' ) }
272
+
273
+ let(:new_attributes) { { released_on: '2011-11-11', release_price: 59.99 } }
274
+
275
+ it 'should alter the attributes and save the model' do
276
+ subject.should receive(:attributes=).with( new_attributes )
277
+ subject.should receive(:save)
278
+ subject.update_attributes( new_attributes )
279
+ end
280
+ end
281
+
282
+ describe ':update_attributes!' do
283
+ subject { Game.new( id: 'abcd1234', title: 'Skyrim' ) }
284
+
285
+ let(:new_attributes) { { released_on: '2011-11-11', release_price: 59.99 } }
286
+
287
+ it 'should alter the attributes and save! the model' do
288
+ subject.should receive(:attributes=).with( new_attributes )
289
+ subject.should receive(:save!)
290
+ subject.update_attributes!( new_attributes )
291
+ end
292
+ end
293
+
294
+ describe ':touch' do
295
+ subject { Game.new( id: 'abcd1234', title: 'Skyrim' ) }
296
+
297
+ before do
298
+ stub_request( :put, 'https://api.parse.com/1/classes/Game/abcd1234' ).with(
299
+ body: { },
300
+ headers: { 'Content-Type' => 'application/json' }
301
+ ).to_return(
302
+ body: { updatedAt: '2014-12-18T15:00:00Z' }.to_json,
303
+ status: 200,
304
+ headers: { content_type: 'application/json', Location: 'https://api.parse.com/1/classes/Game/abcd1234' }
305
+ )
306
+ end
307
+
308
+ it do
309
+ expect { subject.touch }.to change( subject, :updated_at ).from( nil ).to( '2014-12-18T15:00:00Z'.to_datetime )
310
+ end
311
+ end
312
+
313
+ describe 'when deleting a new model' do
314
+ subject { Game.new( title: 'Skyrim' ) }
315
+
316
+ it 'should not receive :http_delete' do
317
+ subject.class.should_not receive( :http_delete )
318
+ subject.delete
319
+ end
320
+
321
+ it 'should be frozen after delete' do
322
+ subject.delete
323
+ subject.should be_frozen
324
+ end
325
+ end
326
+
327
+ describe 'when deleting an existing model' do
328
+ subject { Game.new( id: 'abcd1234', title: 'Skyrim' ) }
329
+
330
+ it 'should receive :http_delete' do
331
+ subject.class.should receive( :http_delete ).with('abcd1234', {})
332
+ subject.delete
333
+ end
334
+
335
+ it 'should be frozen after delete' do
336
+ stub_request( :delete, 'https://api.parse.com/1/classes/Game/abcd1234' ).to_return( status: 200 )
337
+
338
+ subject.delete
339
+ subject.should be_frozen
340
+ end
341
+ end
342
+
343
+ describe ':pointer' do
344
+ subject { Game.new( id: 'abcd1234' ) }
345
+
346
+ it 'should be an Opium::Pointer' do
347
+ subject.pointer.should be_a( Opium::Pointer )
348
+ end
349
+
350
+ it 'should have the :id of the instance' do
351
+ subject.pointer.id.should == subject.id
352
+ end
353
+
354
+ it 'should have the :model_name of the instance class' do
355
+ subject.pointer.model_name.should == subject.class.model_name
356
+ end
357
+ end
358
+
359
+ describe ':to_parse' do
360
+ subject { Game.new( id: 'abcd1234' ) }
361
+
362
+ it 'should be a pointer hash' do
363
+ subject.to_parse.should == subject.pointer.to_parse
364
+ end
365
+ end
366
+ end
367
+ end