opium 1.2.3 → 1.2.4

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.
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