opium 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df649015596245c055a437ab8d6946fa2c6badd7
4
- data.tar.gz: 35c58b1cc03b4bc34c4ea74fae80eafa691fa1e6
3
+ metadata.gz: eb9239c124be4e458967d7fe92d4073ea2cb996b
4
+ data.tar.gz: ee2dd7e877cfcd1892659b8645daa4b08cc9fc99
5
5
  SHA512:
6
- metadata.gz: 1bd35e1c4a44a923d3bbc1a2b76800b8e166b98ad450a33ec0a32b3708063224dd283e28bf4ecf5b576d1f783bd57120a556732bf659a654506fe5749957732e
7
- data.tar.gz: 71c2ab4dcc1457000e07abfac7e28165568212b86bdd61564950a605a80cd61c93c28ed82bcde2c68c122815e99b23023c749093e13a53cb4348235764640f62
6
+ metadata.gz: 770af57f72afb14bcf366157f7c3f4b7c628b44e0877e382441f628d2c2379d5e1449c07e57b7f59af6e3f4bc211e616a3d60139facf12d5f8d05d5aaff341d2
7
+ data.tar.gz: 9fb435ce9b0acfe2ebb5a18282cc6e5adb684fe5e83a818322cab5d1f04c213fb6fcdc9ff835690e9670016302fd2004da30607cfa7693415b18ff7b0cee3973
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.5.3
2
+ ### New Features
3
+ - #54: Installation queries are now supported by Opium::Push to perform a more finely targeted push.
4
+ - #55: Introduction of Opium::Installation, which allows for access and manipulation of Parse's Installation object.
5
+ - Opium::Push has an expanded set of attributes which may be set to further customize a push, including the ability to schedule a push for a particular time and expire pushes.
6
+
1
7
  ## 1.5.2
2
8
  ### Resolved Issues
3
9
  - Push should now use the master key for creating notifications, rather than the REST API key.
data/README.md CHANGED
@@ -56,12 +56,20 @@ A generator exists for creating new models; this should be invoked whenever `rai
56
56
  $ rails g model game title:string price:float
57
57
  ```
58
58
 
59
- A separate generate is available for creating a model to wrap Parse's User model:
59
+ A separate generator is available for creating a model to wrap Parse's User model:
60
60
 
61
61
  ```bash
62
62
  $ rails g opium:user
63
63
  ```
64
64
 
65
+ Finally, another generator is available to further customize Parse's Installation model:
66
+
67
+ ```bash
68
+ $ rails g opium:installation
69
+ ```
70
+
71
+ Both of these latter two generators otherwise accept the same arguments as the generic model generator.
72
+
65
73
  ### Specifying a model
66
74
 
67
75
  Models are defined by mixing in `Opium::Model` into a new class. Class names should match the names of the
@@ -0,0 +1,16 @@
1
+ require 'rails/generators'
2
+ require 'generators/opium/model_generator'
3
+
4
+ module Opium
5
+ module Generators
6
+ class InstallationGenerator < ::Rails::Generators::Base
7
+ desc "Creates an Opium installation model"
8
+
9
+ argument :attributes, type: :array, default: [], banner: "field:type field:type"
10
+
11
+ def run_model_generator
12
+ generate 'model', *['installation', *attributes, '--parent=opium/installation']
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module Opium
2
+ class Installation
3
+ include Opium::Model
4
+
5
+ no_object_prefix!
6
+ requires_heightened_privileges!
7
+
8
+ field :badge, type: Integer
9
+ field :channels, type: Array
10
+ field :time_zone, type: String
11
+ field :device_type, type: Symbol, readonly: true
12
+ field :push_type, type: Symbol, readonly: true
13
+ field :gcm_sender_id, type: Integer
14
+ field :installation_id, type: String, readonly: true
15
+ field :device_token, type: String
16
+ field :channel_uris, type: Array
17
+ field :app_name, type: String
18
+ field :app_version, type: String
19
+ field :parse_version, type: String
20
+ field :app_identifier, type: String
21
+ end
22
+ end
data/lib/opium/push.rb CHANGED
@@ -4,25 +4,46 @@ module Opium
4
4
  class Push
5
5
  include Opium::Model::Connectable
6
6
 
7
+ class << self
8
+ private
9
+
10
+ def attr_hash_accessors( hash_name, *methods )
11
+ methods.each do |method_name|
12
+ attr_hash_accessor( hash_name, method_name )
13
+ end
14
+ end
15
+
16
+ def attr_hash_accessor( hash_name, method_name )
17
+ unless respond_to?( method_name )
18
+ define_method method_name do
19
+ self.send( hash_name )[ method_name ]
20
+ end
21
+ end
22
+ setter = "#{ method_name }="
23
+ unless respond_to?( setter )
24
+ define_method setter do |value|
25
+ self.send( hash_name )[ method_name ] = value
26
+ end
27
+ end
28
+ end
29
+ end
30
+
7
31
  requires_heightened_privileges!
8
32
 
9
33
  def initialize( attributes = {} )
10
34
  self.channels = []
11
35
  self.data = {}.with_indifferent_access
36
+ attributes.each {|k, v| self.send( "#{k}=", v )}
12
37
  end
13
38
 
14
- attr_accessor :channels, :data
39
+ attr_accessor :channels, :where, :data, :push_at, :expires_at, :expiration_interval
15
40
 
16
- def alert
17
- data[:alert]
18
- end
41
+ alias_method :criteria, :where
42
+ alias_method :criteria=, :where=
19
43
 
20
- def alert=( value )
21
- self.data[:alert] = value
22
- end
44
+ attr_hash_accessors :data, :alert, :badge, :sound, :content_available, :category, :uri, :title
23
45
 
24
46
  def create
25
- fail ArgumentError, 'No channels were specified!' if channels.empty?
26
47
  self.class.as_resource(:push) do
27
48
  result = self.class.http_post post_data
28
49
  result[:result]
@@ -32,10 +53,37 @@ module Opium
32
53
  private
33
54
 
34
55
  def post_data
35
- {
36
- channels: self.channels,
37
- data: self.data
38
- }
56
+ {}.tap do |pd|
57
+ targetize!( pd )
58
+ schedulize!( pd )
59
+ pd[:data] = data
60
+ end
61
+ end
62
+
63
+ def targetize!( hash )
64
+ if criteria
65
+ c = criteria
66
+ c = Installation.where( c ) unless c.is_a?( Opium::Model::Criteria )
67
+ c = c.and( channels: channels ) unless channels.empty?
68
+ hash[:where] = c.constraints[:where]
69
+ elsif !channels.empty?
70
+ hash[:channels] = channels
71
+ else
72
+ fail ArgumentError, 'No channels or criteria were specified!'
73
+ end
74
+ end
75
+
76
+ def schedulize!( hash )
77
+ fail ArgumentError, 'No scheduled time for #push_at specified!' if expiration_interval && !push_at
78
+ if push_at
79
+ fail ArgumentError, 'Can only schedule a push up to 2 weeks in advance!' if push_at > ( Time.now + ( 2 * 604800 ) )
80
+ fail ArgumentError, 'Cannot schedule pushes in the past... unless you are the Doctor' if push_at < Time.now
81
+ hash[:push_time] = push_at.iso8601
82
+ hash[:expiration_interval] = expiration_interval
83
+ elsif expires_at
84
+ fail ArgumentError, 'Cannot schedule expiration in the past... unless you have a TARDIS' if expires_at < Time.now
85
+ hash[:expiration_time] = expires_at.iso8601
86
+ end
39
87
  end
40
88
  end
41
89
  end
data/lib/opium/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Opium
2
- VERSION = "1.5.2"
2
+ VERSION = "1.5.3"
3
3
  end
data/lib/opium.rb CHANGED
@@ -6,6 +6,7 @@ require 'opium/config'
6
6
  require 'opium/extensions'
7
7
  require 'opium/model'
8
8
  require 'opium/user'
9
+ require 'opium/installation'
9
10
  require 'opium/file'
10
11
  require 'opium/schema'
11
12
  require 'opium/push'
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::Installation do
4
+
5
+ it { described_class.should respond_to( :object_prefix ) }
6
+
7
+ it { should be_an( Opium::Model ) }
8
+
9
+ it { expect( described_class ).to have_heightened_privileges }
10
+
11
+ describe '#object_prefix' do
12
+ it { expect( described_class.object_prefix ).to be_empty }
13
+ end
14
+
15
+ describe '#resource_name' do
16
+ it { expect( described_class.resource_name ).to eq 'installations' }
17
+ end
18
+
19
+ %w{ badge channels time_zone device_type push_type gcm_sender_id
20
+ installation_id device_token channel_uris app_name app_version
21
+ parse_version app_identifier }.each do |field_name|
22
+ it { described_class.fields.should have_key( field_name ) }
23
+ end
24
+
25
+ %w{ device_type push_type installation_id }.each do |field_name|
26
+ describe "##{ field_name }" do
27
+ it { expect( described_class.fields[field_name] ).to be_readonly }
28
+ end
29
+ end
30
+
31
+ context 'within a subclass' do
32
+ before do
33
+ stub_const( 'SpecialInstallation', Class.new(Opium::Installation) do
34
+ field :has_web_access, type: Opium::Boolean
35
+ end )
36
+ end
37
+
38
+ subject { SpecialInstallation }
39
+
40
+ it { is_expected.to be <= Opium::Installation }
41
+ it { is_expected.to respond_to( :field, :fields ) }
42
+ it { expect( subject.fields.keys ).to include( 'badge', 'device_token', 'has_web_access' ) }
43
+
44
+ it { expect( subject ).to have_heightened_privileges }
45
+
46
+ describe '#object_prefix' do
47
+ it { expect( subject.object_prefix ).to be_empty }
48
+ end
49
+
50
+ describe '#resource_name' do
51
+ it { expect( subject.resource_name ).to eq 'installations' }
52
+ end
53
+ end
54
+ end
@@ -5,73 +5,128 @@ describe Opium::Push do
5
5
 
6
6
  it { expect( described_class ).to respond_to(:to_ruby, :to_parse).with(1).argument }
7
7
 
8
- it { is_expected.to respond_to( :create, :channels, :data, :alert ) }
8
+ it { is_expected.to respond_to( :create, :channels, :where, :data, :alert, :badge, :sound, :content_available, :category, :uri, :title, :expires_at, :push_at, :expiration_interval ) }
9
9
 
10
- describe '#alert' do
10
+ shared_examples_for 'a push option getter' do |option, value|
11
11
  let(:result) do
12
12
  subject.data = data
13
- subject.alert
13
+ subject.send(option)
14
14
  end
15
15
 
16
16
  context 'with no data' do
17
17
  let(:data) { { } }
18
18
 
19
- it 'equals data[:alert]' do
19
+ it "equals data[:#{ option }]" do
20
20
  expect( result ).to be_nil
21
21
  end
22
22
  end
23
23
 
24
- context 'with alert data' do
25
- let(:data) { { alert: 'The sky is blue.' } }
24
+ context "with a value" do
25
+ let(:data) { { option => value } }
26
26
 
27
- it 'equals data[:alert]' do
28
- expect( result ).to eq data[:alert]
27
+ it "equals data[:#{ option }]" do
28
+ expect( result ).to eq data[option]
29
29
  end
30
30
  end
31
31
  end
32
32
 
33
- describe '#alert=' do
33
+ shared_examples_for 'a push option setter' do |option, value|
34
34
  let(:result) do
35
- subject.alert = alert
36
- subject.data[:alert]
35
+ subject.send( "#{ option }=".to_sym, option_value )
36
+ subject.data[option]
37
37
  end
38
38
 
39
39
  context 'with nothing' do
40
- let(:alert) { nil }
40
+ let(:option_value) { nil }
41
41
 
42
- it 'equals data[:alert]' do
42
+ it "equals data[:#{ option }]" do
43
43
  expect( result ).to be_nil
44
44
  end
45
45
  end
46
46
 
47
- context 'with text' do
48
- let(:alert) { 'The sky is blue.' }
47
+ context 'with a value' do
48
+ let(:option_value) { value }
49
49
 
50
- it 'equals data[:alert]' do
51
- expect( result ).to eq alert
50
+ it "equals data[:#{ option }]" do
51
+ expect( result ).to eq option_value
52
52
  end
53
53
  end
54
54
  end
55
55
 
56
+ {
57
+ alert: 'The sky is blue.',
58
+ badge: 'Increment',
59
+ sound: 'cheering.caf',
60
+ content_available: 1,
61
+ category: 'A category',
62
+ uri: 'https://example.com',
63
+ title: 'Current Weather'
64
+ }.each do |option, value|
65
+ describe "##{ option }" do
66
+ it_behaves_like 'a push option getter', option, value
67
+ end
68
+
69
+ describe "##{ option }=" do
70
+ it_behaves_like 'a push option setter', option, value
71
+ end
72
+ end
73
+
56
74
  describe '#create' do
57
75
  let(:result) do
58
- subject.tap do |push|
59
- push.channels = channels
60
- push.alert = alert
61
- end.create
76
+ push.create
62
77
  end
63
78
 
79
+ let(:push) do
80
+ subject.tap do |p|
81
+ p.channels = channels if channels
82
+ p.where = criteria if criteria
83
+ p.alert = alert
84
+ p.push_at = push_at if push_at
85
+ p.expires_at = expires_at if expires_at
86
+ p.expiration_interval = expiration_interval if expiration_interval
87
+ end
88
+ end
89
+
90
+ let(:push_post_data) { push.send(:post_data) }
91
+
64
92
  let(:alert) { 'Zoo animals are fighting!' }
93
+ let(:channels) { %w{ General } }
94
+ let(:criteria) { nil }
95
+ let(:push_at) { nil }
96
+ let(:expires_at) { nil }
97
+ let(:expiration_interval) { nil }
98
+
99
+ let(:one_day_ago) { Time.now - 86400 }
100
+ let(:one_day_from_now) { Time.now + 86400 }
101
+ let(:one_week) { 604800 }
102
+ let(:one_week_from_now) { Time.now + one_week }
103
+ let(:three_weeks_from_now) { Time.now + ( 3 * one_week ) }
65
104
 
66
105
  before do
67
106
  stub_request(:post, "https://api.parse.com/1/push").
68
107
  with(body: "{\"channels\":[\"Penguins\",\"PolarBears\"],\"data\":{\"alert\":\"Zoo animals are fighting!\"}}",
69
- headers: {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key' => 'PARSE_MASTER_KEY'}).
70
- to_return(:status => 200, :body => { result: true }.to_json, :headers => {content_type: 'application/json'})
108
+ headers: {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key' => 'PARSE_MASTER_KEY'}).
109
+ to_return(status: 200, body: { result: true }.to_json, headers: {content_type: 'application/json'})
110
+
111
+ stub_request(:post, "https://api.parse.com/1/push").
112
+ with(body: "{\"where\":{\"deviceType\":\"ios\"},\"data\":{\"alert\":\"Zoo animals are fighting!\"}}",
113
+ headers: {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
114
+ to_return(status: 200, body: { result: true }.to_json, headers: { content_type: 'application/json' })
115
+
116
+ stub_request(:post, "https://api.parse.com/1/push").
117
+ with(body: "{\"where\":{\"deviceType\":\"ios\",\"channels\":[\"Penguins\",\"PolarBears\"]},\"data\":{\"alert\":\"Zoo animals are fighting!\"}}",
118
+ headers: {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
119
+ to_return(status: 200, body: { result: true }.to_json, headers: { content_type: 'application/json' })
120
+
121
+ stub_request(:post, "https://api.parse.com/1/push").
122
+ with(body: "{\"where\":{\"deviceType\":\"ios\",\"badge\":{\"$lte\":5},\"channels\":[\"Penguins\",\"PolarBears\"]},\"data\":{\"alert\":\"Zoo animals are fighting!\"}}",
123
+ headers: {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
124
+ to_return(status: 200, body: { result: true }.to_json, headers: { content_type: 'application/json' })
71
125
  end
72
126
 
73
- context 'with no channels' do
127
+ context 'with no channels or criteria' do
74
128
  let(:channels) { [] }
129
+ let(:criteria) { nil }
75
130
 
76
131
  it { expect { result }.to raise_exception( ArgumentError ) }
77
132
  end
@@ -79,8 +134,121 @@ describe Opium::Push do
79
134
  context 'with channels' do
80
135
  let(:channels) { %w{ Penguins PolarBears } }
81
136
 
137
+ it 'sets the channels key' do
138
+ expect( push_post_data ).to include( channels: channels )
139
+ end
140
+ it 'does not set the where key' do
141
+ expect( push_post_data ).to_not include( :where )
142
+ end
143
+ it { expect { result }.to_not raise_exception }
144
+ it { expect( result ).to eq true }
145
+ end
146
+
147
+ context 'with a criteria' do
148
+ let(:channels) { nil }
149
+ let(:criteria) { { device_type: :ios } }
150
+
151
+ it 'does not set the channels key' do
152
+ expect( push_post_data ).to_not include( :channels )
153
+ end
154
+ it 'sets the where key' do
155
+ expect( push_post_data ).to include( :where )
156
+ end
157
+ it 'sets the where key to a parse-friendly version' do
158
+ expect( push_post_data[:where] ).to eq( { 'deviceType' => :ios } )
159
+ end
160
+
161
+ it { expect { result }.to_not raise_exception }
162
+ it { expect( result ).to eq true }
163
+ end
164
+
165
+ context 'with channels and a criteria' do
166
+ let(:channels) { %w{ Penguins PolarBears } }
167
+ let(:criteria) { { device_type: :ios } }
168
+
169
+ it 'does not set the channels key' do
170
+ expect( push_post_data ).to_not include( :channels )
171
+ end
172
+ it 'sets the where key' do
173
+ expect( push_post_data ).to include( :where )
174
+ end
175
+ it 'adds the channels to the criteria' do
176
+ expect( push_post_data[:where] ).to include( channels: channels )
177
+ end
178
+
179
+ it { expect { result }.to_not raise_exception }
180
+ it { expect( result ).to eq true }
181
+ end
182
+
183
+ context 'with an installation criteria and channels' do
184
+ let(:channels) { %w{ Penguins PolarBears } }
185
+ let(:criteria) { Opium::Installation.where( device_type: :ios ).lte( badge: 5 ) }
186
+
187
+ it 'does not set the channels key' do
188
+ expect( push_post_data ).to_not include( :channels )
189
+ end
190
+ it 'sets the where key' do
191
+ expect( push_post_data ).to include( :where )
192
+ end
193
+ it 'adds the channels to the criteria' do
194
+ expect( push_post_data[:where] ).to include( channels: channels )
195
+ end
196
+ it 'uses the installation criterias constraints' do
197
+ expect( push_post_data[:where] ).to include( :badge )
198
+ expect( push_post_data[:where][:badge] ).to include( '$lte' => 5 )
199
+ end
200
+
82
201
  it { expect { result }.to_not raise_exception }
83
202
  it { expect( result ).to eq true }
84
203
  end
204
+
205
+ context 'with a scheduled push' do
206
+ let(:push_at) { one_day_from_now }
207
+
208
+ it { expect { push_post_data }.not_to raise_exception }
209
+ it 'sets the proper push_time' do
210
+ expect( push_post_data ).to include( push_time: push_at.iso8601 )
211
+ end
212
+ end
213
+
214
+ context 'with a scheduled push before now' do
215
+ let(:push_at) { one_day_ago }
216
+
217
+ it { expect { result }.to raise_exception( ArgumentError ) }
218
+ end
219
+
220
+ context 'with a scheduled push too long from now' do
221
+ let(:push_at) { three_weeks_from_now }
222
+
223
+ it { expect { result }.to raise_exception( ArgumentError ) }
224
+ end
225
+
226
+ context 'with a scheduled push and an expiration inverval' do
227
+ let(:push_at) { one_day_from_now }
228
+ let(:expiration_interval) { one_week }
229
+
230
+ it { expect { push_post_data }.not_to raise_exception }
231
+ it 'sets the proper push_time' do
232
+ expect( push_post_data ).to include( push_time: push_at.iso8601 )
233
+ end
234
+ it 'sets the proper expiration_interval' do
235
+ expect( push_post_data ).to include( expiration_interval: expiration_interval )
236
+ end
237
+ end
238
+
239
+ context 'without a scheduled push and with an expiration inverval' do
240
+ let(:expiration_interval) { one_week }
241
+
242
+ it { expect { result }.to raise_exception( ArgumentError ) }
243
+ end
244
+
245
+ context 'with an expiry' do
246
+ let(:expires_at) { one_week_from_now }
247
+
248
+ it { expect { push_post_data }.not_to raise_exception }
249
+ it 'sets the proper expiration_time' do
250
+ expect( push_post_data ).to include( expiration_time: expires_at.iso8601 )
251
+ end
252
+ end
85
253
  end
86
254
  end
data/spec/opium_spec.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Opium do
4
- it { expect( described_class.constants ).to include( :Model, :User, :File, :Config, :Schema, :Push ) }
4
+ it { expect( described_class.constants ).to include( :Model, :User, :Installation, :File, :Config, :Schema, :Push ) }
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opium
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Bowers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-28 00:00:00.000000000 Z
11
+ date: 2017-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -252,6 +252,7 @@ files:
252
252
  - README.md
253
253
  - Rakefile
254
254
  - lib/generators/opium/config_generator.rb
255
+ - lib/generators/opium/installation_generator.rb
255
256
  - lib/generators/opium/model_generator.rb
256
257
  - lib/generators/opium/templates/config.yml
257
258
  - lib/generators/opium/templates/model.rb
@@ -277,6 +278,7 @@ files:
277
278
  - lib/opium/extensions/time.rb
278
279
  - lib/opium/extensions/true_class.rb
279
280
  - lib/opium/file.rb
281
+ - lib/opium/installation.rb
280
282
  - lib/opium/model.rb
281
283
  - lib/opium/model/attributable.rb
282
284
  - lib/opium/model/batchable.rb
@@ -324,6 +326,7 @@ files:
324
326
  - spec/opium/extensions/symbol_spec.rb
325
327
  - spec/opium/extensions/time_spec.rb
326
328
  - spec/opium/file_spec.rb
329
+ - spec/opium/installation_spec.rb
327
330
  - spec/opium/model/attributable_spec.rb
328
331
  - spec/opium/model/batchable/batch_spec.rb
329
332
  - spec/opium/model/batchable/operation_spec.rb
@@ -394,6 +397,7 @@ test_files:
394
397
  - spec/opium/extensions/symbol_spec.rb
395
398
  - spec/opium/extensions/time_spec.rb
396
399
  - spec/opium/file_spec.rb
400
+ - spec/opium/installation_spec.rb
397
401
  - spec/opium/model/attributable_spec.rb
398
402
  - spec/opium/model/batchable/batch_spec.rb
399
403
  - spec/opium/model/batchable/operation_spec.rb