opium 1.2.3 → 1.2.4

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: af0eabf84dc2725b37a028ab361dace64cf64b77
4
- data.tar.gz: e3220606ae49fdb2322b2a008b36a44eb47d8131
3
+ metadata.gz: 20938c9338f0dea54366b7ae20a74a5e416c1b3e
4
+ data.tar.gz: 901e5c2f20e9db92da93f2e32fd0c64bc6323269
5
5
  SHA512:
6
- metadata.gz: c79a1131fbe18550a72aa80a425b040f39423e4cfa3c57097dc95bd64883f30632a1e549fdfece5f26c64f8a338d6cc9e1f8ae6501b49cd902c5e40990520626
7
- data.tar.gz: d98dc79d4d4919202d4374908b9b57a4c9fd5ec1444c555223a06520fea777de6efee82ce650257b434b4aed8eb9cf7f6bae46399f1fd2dfff3b0f0794599a19
6
+ metadata.gz: 812b467de4ac66ffedd6b3bf7be8965b09af23a3a9bd8253dfac3ecf4cc7dc1608458bb27e3f116142ef77d220f83c3b4b6d1d4d11230859c0fbef1d9021a89f
7
+ data.tar.gz: f5f78274f8963237dee6db4dd81d27e383d3440a550f7a5105e0ae2ce3769ec5b71d13149d79d6ffe56839dac0c727061d872ec695ac289ae299539f773d0811
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 1.2.4
2
+ ### Resolved Issues
3
+ - #49: Opium::File#to_ruby now correctly handles blank/empty strings.
4
+
1
5
  ## 1.2.3
2
6
  ### New Features
3
7
  - Config file can now track the Parse API webhook key.
data/lib/opium/file.rb CHANGED
@@ -21,7 +21,7 @@ module Opium
21
21
  end
22
22
 
23
23
  def to_ruby( object )
24
- return unless object
24
+ return if object.nil? || object == ''
25
25
  return object if object.is_a?( self )
26
26
  object = ::JSON.parse( object ) if object.is_a?( String )
27
27
  if object.is_a?( Hash ) && (has_key_of_value( object, :__type, 'File' ) || has_keys( object, :url, :name ))
@@ -5,19 +5,19 @@ module Opium
5
5
  module Model
6
6
  module Connectable
7
7
  extend ActiveSupport::Concern
8
-
8
+
9
9
  included do
10
10
  end
11
-
11
+
12
12
  class ParseError < StandardError
13
13
  def initialize( code, error )
14
14
  super( error )
15
15
  @code = code
16
16
  end
17
-
17
+
18
18
  attr_reader :code
19
19
  end
20
-
20
+
21
21
  module ClassMethods
22
22
  def connection
23
23
  @@connection ||= Faraday.new( url: 'https://api.parse.com/1/' ) do |faraday|
@@ -30,20 +30,20 @@ module Opium
30
30
  faraday.adapter Faraday.default_adapter
31
31
  end
32
32
  end
33
-
33
+
34
34
  def reset_connection!
35
35
  @@connection = nil
36
36
  end
37
-
37
+
38
38
  def object_prefix
39
39
  @object_prefix ||= 'classes'
40
40
  end
41
-
41
+
42
42
  # Parse doesn't route User objects through /classes/, instead treating them as a top-level class.
43
43
  def no_object_prefix!
44
44
  @object_prefix = ''
45
45
  end
46
-
46
+
47
47
  def as_resource( name, &block )
48
48
  fail ArgumentError, 'no block given' unless block_given?
49
49
  @masked_resource_name = name.to_s.freeze
@@ -51,13 +51,13 @@ module Opium
51
51
  ensure
52
52
  @masked_resource_name = nil
53
53
  end
54
-
54
+
55
55
  def resource_name( resource_id = nil )
56
56
  return @masked_resource_name if @masked_resource_name
57
57
  @resource_name ||= Pathname.new( object_prefix ).join( map_name_to_resource( model_name ) )
58
58
  ( resource_id ? @resource_name.join( resource_id ) : @resource_name ).to_s
59
59
  end
60
-
60
+
61
61
  def http_get( options = {} )
62
62
  http( :get, options ) do |request|
63
63
  options.fetch(:query, {}).each do |key, value|
@@ -65,38 +65,42 @@ module Opium
65
65
  end
66
66
  end
67
67
  end
68
-
68
+
69
69
  def http_post( data, options = {} )
70
70
  http( :post, deeply_merge( options, content_type_json ), &infuse_request_with( data ) )
71
71
  end
72
-
72
+
73
73
  def http_put( id, data, options = {} )
74
- http( :put, deeply_merge( options, content_type_json, id: id ), &infuse_request_with( data ) )
74
+ http( :put, deeply_merge( options, content_type_json, id: id ), &infuse_request_with( data ) )
75
75
  end
76
-
76
+
77
77
  def http_delete( id, options = {} )
78
78
  http( :delete, deeply_merge( options, id: id ) )
79
79
  end
80
-
80
+
81
81
  def requires_heightened_privileges!
82
82
  @requires_heightened_privileges = true
83
83
  end
84
-
84
+
85
85
  def requires_heightened_privileges?
86
86
  !@requires_heightened_privileges.nil?
87
87
  end
88
-
88
+
89
89
  alias_method :has_heightened_privileges?, :requires_heightened_privileges?
90
-
90
+
91
91
  def with_heightened_privileges(&block)
92
92
  previous, @requires_heightened_privileges = @requires_heightened_privileges, true
93
93
  block.call if block_given?
94
94
  ensure
95
95
  @requires_heightened_privileges = previous
96
96
  end
97
-
97
+
98
+ def no_really_i_need_master!
99
+ @always_heightened_privileges = true
100
+ end
101
+
98
102
  private
99
-
103
+
100
104
  def http( method, options, &block )
101
105
  check_for_error( options ) do
102
106
  if options[:sent_headers]
@@ -109,43 +113,46 @@ module Opium
109
113
  end
110
114
  end
111
115
  end
112
-
116
+
113
117
  def deeply_merge( *args )
114
118
  args.reduce {|a, e| a.deep_merge e }
115
119
  end
116
-
120
+
117
121
  def content_type_json
118
122
  @content_type_json ||= { headers: { content_type: 'application/json' } }
119
123
  end
120
-
124
+
121
125
  def map_name_to_resource( model_name )
122
126
  name = model_name.name.demodulize
123
127
  @object_prefix.empty? ? name.tableize : name
124
128
  end
125
-
129
+
126
130
  def infuse_request_with( data )
127
131
  lambda do |request|
128
132
  request.body = data
129
133
  end
130
134
  end
131
-
135
+
132
136
  def apply_headers_to_request( method, options, &further_operations )
133
137
  lambda do |request|
134
138
  request.headers.update options[:headers] if options[:headers]
135
139
 
136
- added_master_key =
137
- unless request.headers[:x_parse_session_token]
138
- if method != :get && requires_heightened_privileges? && Opium.config.master_key
139
- request.headers[:x_parse_master_key] = Opium.config.master_key
140
- end
141
- end
142
-
143
- request.headers[:x_parse_rest_api_key] = Opium.config.api_key unless added_master_key
144
-
140
+ if use_master_key?( request, method )
141
+ request.headers[:x_parse_master_key] = Opium.config.master_key
142
+ else
143
+ request.headers[:x_parse_rest_api_key] = Opium.config.api_key
144
+ end
145
+
145
146
  further_operations.call( request ) if block_given?
146
147
  end
147
148
  end
148
-
149
+
150
+ def use_master_key?( request, method )
151
+ !request.headers[:x_parse_session_token] &&
152
+ ( @always_heightened_privileges || method != :get ) &&
153
+ requires_heightened_privileges? && Opium.config.master_key
154
+ end
155
+
149
156
  def check_for_error( options = {}, &block )
150
157
  fail ArgumentError, 'no block given' unless block_given?
151
158
  result = yield
@@ -161,4 +168,4 @@ module Opium
161
168
  end
162
169
  end
163
170
  end
164
- end
171
+ end
@@ -4,9 +4,9 @@ module Opium
4
4
  def initialize(name, type, default, readonly, as)
5
5
  self.name, self.type, self.default, self.readonly, self.as = name, type, default, readonly, as
6
6
  end
7
-
7
+
8
8
  attr_reader :name, :type, :readonly, :as
9
-
9
+
10
10
  def default( context = nil )
11
11
  if @default.respond_to? :call
12
12
  params = []
@@ -16,30 +16,34 @@ module Opium
16
16
  @default
17
17
  end
18
18
  end
19
-
19
+
20
20
  def contextual_default_value( context = nil)
21
21
  type.to_ruby( default( context ) )
22
22
  end
23
-
23
+
24
24
  def readonly?
25
25
  self.readonly == true
26
26
  end
27
-
27
+
28
28
  def relation?
29
29
  self.type == Relation
30
30
  end
31
-
31
+
32
32
  def virtual?
33
33
  relation? || self.type == Reference
34
34
  end
35
-
35
+
36
36
  def name_to_parse
37
37
  @name_to_parse ||= (self.as || self.name).to_s.camelize(:lower)
38
38
  end
39
-
39
+
40
+ def name_to_ruby
41
+ @name_to_ruby ||= (self.as || self.name).to_s.underscore
42
+ end
43
+
40
44
  private
41
-
45
+
42
46
  attr_writer :name, :type, :default, :readonly, :as
43
47
  end
44
48
  end
45
- end
49
+ end
@@ -4,13 +4,13 @@ module Opium
4
4
  module Model
5
5
  module Fieldable
6
6
  extend ActiveSupport::Concern
7
-
7
+
8
8
  included do
9
9
  field :id, type: String, readonly: true, as: :object_id
10
10
  field :created_at, type: DateTime, readonly: true
11
11
  field :updated_at, type: DateTime, readonly: true
12
12
  end
13
-
13
+
14
14
  module ClassMethods
15
15
  def field( name, options = {} )
16
16
  create_field_from( name.to_sym, options ).tap do |field|
@@ -18,38 +18,38 @@ module Opium
18
18
  create_field_setter_for( field )
19
19
  end
20
20
  end
21
-
21
+
22
22
  def has_field?( field_name )
23
23
  fields.key? field_name
24
24
  end
25
-
25
+
26
26
  alias_method :field?, :has_field?
27
-
27
+
28
28
  def fields
29
29
  @fields ||= {}.with_indifferent_access
30
30
  end
31
-
31
+
32
32
  def ruby_canonical_field_names
33
33
  @ruby_canonical_field_names ||= {}.with_indifferent_access
34
34
  end
35
-
35
+
36
36
  def parse_canonical_field_names
37
37
  @parse_canonical_field_names ||= {}.with_indifferent_access
38
38
  end
39
-
39
+
40
40
  def default_attributes( context = nil )
41
41
  fields.transform_values {|field| field.contextual_default_value( context ) }.with_indifferent_access
42
42
  end
43
-
43
+
44
44
  private
45
-
45
+
46
46
  def create_field_from( name, options )
47
47
  field = Field.new( name, options[:type] || Object, options[:default], options[:readonly] || false, options[:as] )
48
48
  ruby_canonical_field_names[name] = ruby_canonical_field_names[field.name_to_parse] = name.to_s
49
49
  parse_canonical_field_names[name] = parse_canonical_field_names[field.name_to_parse] = field.name_to_parse.to_s
50
50
  fields[name] = field
51
51
  end
52
-
52
+
53
53
  def create_field_getter_for( field )
54
54
  class_eval do
55
55
  define_attribute_methods [field.name]
@@ -58,7 +58,7 @@ module Opium
58
58
  end
59
59
  end
60
60
  end
61
-
61
+
62
62
  def create_field_setter_for( field )
63
63
  class_eval do
64
64
  define_method("#{ field.name }=") do |value|
@@ -66,7 +66,7 @@ module Opium
66
66
  send( "#{ field.name }_will_change!" ) unless self.attributes[field.name] == converted
67
67
  if field.relation?
68
68
  converted = field.contextual_default_value( self ) unless converted
69
- converted.owner ||= self
69
+ converted.owner ||= self
70
70
  converted.metadata ||= self.class.relations[field.name]
71
71
  end
72
72
  self.attributes[field.name] = converted
@@ -77,4 +77,4 @@ module Opium
77
77
  end
78
78
  end
79
79
  end
80
- end
80
+ end
data/lib/opium/model.rb CHANGED
@@ -3,6 +3,7 @@ require 'active_support/core_ext/string'
3
3
  require 'active_support/core_ext/hash/indifferent_access'
4
4
  require 'active_support/core_ext/hash/deep_merge'
5
5
  require 'active_support/core_ext/hash/transform_values'
6
+ require 'active_support/core_ext/enumerable'
6
7
  require 'active_support/inflector'
7
8
  require 'opium/model/connectable'
8
9
  require 'opium/model/persistable'
@@ -23,9 +24,9 @@ require 'opium/model/kaminari'
23
24
  module Opium
24
25
  module Model
25
26
  extend ActiveSupport::Concern
26
-
27
+
27
28
  include ActiveModel::Model
28
-
29
+
29
30
  included do
30
31
  include Connectable
31
32
  include Persistable
@@ -42,14 +43,14 @@ module Opium
42
43
  include Relatable
43
44
  include GlobalID::Identification if defined?( GlobalID )
44
45
  end
45
-
46
+
46
47
  def initialize( attributes = {} )
47
48
  self.attributes = attributes
48
49
  end
49
-
50
+
50
51
  def inspect
51
52
  inspected_fields = self.attributes.map {|k, v| [k, v.inspect].join(': ')}.join(', ')
52
53
  "#<#{self.class.model_name} #{inspected_fields}>"
53
54
  end
54
55
  end
55
- end
56
+ end
@@ -0,0 +1,48 @@
1
+ module Opium
2
+ class Schema
3
+ include Opium::Model::Connectable
4
+
5
+ requires_heightened_privileges!
6
+ no_really_i_need_master!
7
+ no_object_prefix!
8
+
9
+ class << self
10
+ def all
11
+ http_get[:results].map {|schema| new( schema ) }.index_by(&:class_name)
12
+ end
13
+
14
+ def find( model_name, options = {} )
15
+ result = http_get( options.merge id: model_name )
16
+ options[:sent_headers] ? result : new( result )
17
+ end
18
+
19
+ def model_name
20
+ @model_name ||= ActiveModel::Name.new( self, nil, self.name )
21
+ end
22
+ end
23
+
24
+
25
+ attr_reader :class_name, :fields
26
+
27
+ def initialize( attributes = {} )
28
+ attributes.deep_symbolize_keys.tap do |a|
29
+ @class_name = a[:className]
30
+ @fields = ( a[:fields] || {} ).map do |field_name, options|
31
+ Opium::Model::Field.new( field_name, options[:type], nil, false, nil )
32
+ end.index_by(&:name_to_ruby)
33
+ end
34
+ end
35
+
36
+ def save
37
+
38
+ end
39
+
40
+ def delete
41
+
42
+ end
43
+
44
+ def model
45
+
46
+ end
47
+ end
48
+ end
data/lib/opium/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Opium
2
- VERSION = "1.2.3"
2
+ VERSION = "1.2.4"
3
3
  end
data/lib/opium.rb CHANGED
@@ -7,4 +7,5 @@ require 'opium/extensions'
7
7
  require 'opium/model'
8
8
  require 'opium/user'
9
9
  require 'opium/file'
10
- require 'opium/railtie' if defined?( Rails )
10
+ require 'opium/schema'
11
+ require 'opium/railtie' if defined?( Rails )
@@ -132,6 +132,13 @@ describe Opium::File do
132
132
  it { expect( result ).to be_a Opium::File }
133
133
  end
134
134
 
135
+ context 'when given an empty string' do
136
+ let(:object) { '' }
137
+
138
+ it { expect { result }.to_not raise_exception }
139
+ it { expect( result ).to be_nil }
140
+ end
141
+
135
142
  context 'when not given a hash' do
136
143
  let(:object) { 42 }
137
144
 
@@ -0,0 +1,142 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Opium::Schema do
4
+ before do
5
+ stub_const( 'Game', Class.new do |klass|
6
+ include Opium::Model
7
+ field :name, type: String
8
+ field :current_price, type: Float
9
+ end )
10
+ stub_const( 'Player', Class.new do |klass|
11
+ include Opium::Model
12
+ field :player_tag, type: String
13
+ field :player_score, type: Integer
14
+ end )
15
+ end
16
+
17
+ let(:game_schema) do
18
+ {
19
+ className: 'Game',
20
+ fields: {
21
+ objectId: { type: 'String' },
22
+ name: { type: 'String' },
23
+ current_price: { type: 'Number' },
24
+ createdAt: { type: 'Date' },
25
+ updatedAt: { type: 'Date' }
26
+ }
27
+ }
28
+ end
29
+
30
+ let(:player_schema) do
31
+ {
32
+ className: 'Player',
33
+ fields: {
34
+ objectId: { type: 'String' },
35
+ player_tag: { type: 'String' },
36
+ player_score: { type: 'Number' },
37
+ createdAt: { type: 'Date' },
38
+ updatedAt: { type: 'Date' }
39
+ }
40
+ }
41
+ end
42
+
43
+ it { expect( described_class ).to respond_to( :connection, :http_get ) }
44
+ it { expect( described_class ).to have_heightened_privileges }
45
+ it { expect( described_class.object_prefix ).to be_blank }
46
+
47
+ it { expect( described_class ).to respond_to( :find ).with( 1 ).argument }
48
+ it { expect( described_class ).to respond_to( :all ) }
49
+ it { is_expected.to respond_to( :save, :delete, :model, :fields, :class_name ) }
50
+
51
+ describe '.find' do
52
+ let(:result) { described_class.find( model_name, options ) }
53
+ let(:options) { {} }
54
+
55
+ context 'with a sent_headers option' do
56
+ let(:model_name) { 'NoOp' }
57
+ let(:options) { { sent_headers: true } }
58
+
59
+ it { expect { result }.to_not raise_exception }
60
+ it { expect( result.keys ).to include('X-Parse-Master-Key') }
61
+ it { expect( result.keys ).to_not include('X-Parse-Rest-Api-Key') }
62
+ end
63
+
64
+ context 'with a valid model name' do
65
+ let(:model_name) { 'Game' }
66
+
67
+ before do
68
+ stub_request(:get, "https://api.parse.com/1/schemas/Game").
69
+ with(headers: {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
70
+ to_return(status: 200, body: game_schema.to_json, headers: { content_type: 'application/json' })
71
+ end
72
+
73
+ it { expect { result }.to_not raise_exception }
74
+ it { expect( result ).to be_a( Opium::Schema ) }
75
+ it('sets the correct model name from the results') { expect( result.class_name ).to eq 'Game' }
76
+ it { expect( result.fields ).to be_a Hash }
77
+ it { expect( result.fields.values ).to all( be_a Opium::Model::Field ) }
78
+ end
79
+
80
+ context 'with an invalid model name' do
81
+ let(:model_name) { 'DoesNotExist' }
82
+
83
+ before do
84
+ stub_request(:get, "https://api.parse.com/1/schemas/DoesNotExist").
85
+ with(headers: {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
86
+ to_return(status: 400, body: {
87
+ code: 103, error: 'class DoesNotExist does not exist'
88
+ }.to_json, headers: { content_type: 'application/json' })
89
+ end
90
+
91
+ it { expect { result }.to raise_exception( Opium::Model::Connectable::ParseError ) }
92
+ end
93
+ end
94
+
95
+ describe '.all' do
96
+ let(:result) { described_class.all }
97
+
98
+ context 'when there are no classes' do
99
+ before do
100
+ stub_request(:get, "https://api.parse.com/1/schemas").
101
+ with(headers: {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
102
+ to_return(status: 200, body: {
103
+ results: []
104
+ }.to_json, headers: { content_type: 'application/json' })
105
+ end
106
+
107
+ it { expect( result ).to be_empty }
108
+ it { expect( result ).to be_a( Hash ) }
109
+ end
110
+
111
+ context 'when there are multiple classes' do
112
+ before do
113
+ stub_request(:get, "https://api.parse.com/1/schemas").
114
+ with(headers: {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
115
+ to_return(status: 200, body: {
116
+ results: [
117
+ game_schema,
118
+ player_schema
119
+ ]
120
+ }.to_json, headers: { content_type: 'application/json' })
121
+ end
122
+
123
+ let(:expected_keys) { [ 'Game', 'Player' ] }
124
+
125
+ it { expect( result ).to_not be_empty }
126
+ it { expect( result.keys ).to include( *expected_keys ) }
127
+ it { expect( result.values ).to all( be_a( Opium::Schema ) ) }
128
+ end
129
+ end
130
+
131
+ describe '#save' do
132
+
133
+ end
134
+
135
+ describe '#delete' do
136
+
137
+ end
138
+
139
+ describe '#fields' do
140
+
141
+ end
142
+ 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 ) }
5
- end
4
+ it { expect( described_class.constants ).to include( :Model, :User, :File, :Config, :Schema ) }
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.2.3
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Bowers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-29 00:00:00.000000000 Z
11
+ date: 2016-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -302,6 +302,7 @@ files:
302
302
  - lib/opium/model/scopable.rb
303
303
  - lib/opium/model/serialization.rb
304
304
  - lib/opium/railtie.rb
305
+ - lib/opium/schema.rb
305
306
  - lib/opium/user.rb
306
307
  - lib/opium/version.rb
307
308
  - opium.gemspec
@@ -345,6 +346,7 @@ files:
345
346
  - spec/opium/model/scopable_spec.rb
346
347
  - spec/opium/model/serialization_spec.rb
347
348
  - spec/opium/model_spec.rb
349
+ - spec/opium/schema_spec.rb
348
350
  - spec/opium/user_spec.rb
349
351
  - spec/opium_spec.rb
350
352
  - spec/spec_helper.rb
@@ -368,7 +370,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
368
370
  version: '0'
369
371
  requirements: []
370
372
  rubyforge_project:
371
- rubygems_version: 2.4.6
373
+ rubygems_version: 2.4.5.1
372
374
  signing_key:
373
375
  specification_version: 4
374
376
  summary: An Object Parse.com Mapping technology for defining models.
@@ -413,6 +415,7 @@ test_files:
413
415
  - spec/opium/model/scopable_spec.rb
414
416
  - spec/opium/model/serialization_spec.rb
415
417
  - spec/opium/model_spec.rb
418
+ - spec/opium/schema_spec.rb
416
419
  - spec/opium/user_spec.rb
417
420
  - spec/opium_spec.rb
418
421
  - spec/spec_helper.rb