opium 1.0.3 → 1.1.0
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/generators/opium/model_generator.rb +2 -0
- data/lib/opium/config.rb +1 -1
- data/lib/opium/extensions/symbol.rb +13 -0
- data/lib/opium/extensions.rb +1 -0
- data/lib/opium/file.rb +95 -0
- data/lib/opium/model/attributable.rb +5 -5
- data/lib/opium/model/batchable/batch.rb +43 -0
- data/lib/opium/model/batchable/operation.rb +33 -0
- data/lib/opium/model/batchable.rb +10 -0
- data/lib/opium/model/connectable.rb +7 -0
- data/lib/opium/model/fieldable.rb +1 -1
- data/lib/opium/model/persistable.rb +7 -4
- data/lib/opium/model/queryable.rb +2 -2
- data/lib/opium/model.rb +3 -0
- data/lib/opium/version.rb +1 -1
- data/lib/opium.rb +10 -9
- data/opium.gemspec +2 -0
- data/spec/opium/extensions/symbol_spec.rb +54 -0
- data/spec/opium/file_spec.rb +274 -0
- data/spec/opium/model/attributable_spec.rb +30 -16
- data/spec/opium/model/batchable/batch_spec.rb +145 -0
- data/spec/opium/model/batchable/operation_spec.rb +73 -0
- data/spec/opium/model/batchable_spec.rb +5 -0
- data/spec/opium/model/connectable_spec.rb +27 -0
- data/spec/opium/model/fieldable_spec.rb +35 -0
- data/spec/opium/model/persistable_spec.rb +35 -34
- data/spec/opium/model/queryable_spec.rb +35 -25
- data/spec/opium/model_spec.rb +1 -0
- data/spec/opium_spec.rb +1 -1
- metadata +33 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52cfc685abe291743976cf7e5df9bec31c212741
|
4
|
+
data.tar.gz: 346b5aa5edbd26292d06a10319b3b068cbb3c39f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3f799905542a16bdfd274c4c4e591ffab22a56b93f8336c88118ad3319d6d72c8b7d56d822dac81a778c1d8391f69a1a8584bff2218ebb51679de19ca3565d1
|
7
|
+
data.tar.gz: a583321efe297e267264885ac18a84eded3476386c146bcfd009f4adb979b31248fa1a76a15d11c8db165c5441d37f88b5dec86f2380d3b52cb8fc8d5ad13ee9
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
## 1.1.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
- #29: `Opium::File` is now a supported field type, which wraps Parse's File objects.
|
5
|
+
- #31: `Symbol` is now a supported field type, which is meant to be used on top of a Parse String column.
|
6
|
+
|
7
|
+
### Resolved Issues
|
8
|
+
- #30: ActionController compatibility is increased: `.all` and `#update` should work without causing any issues.
|
9
|
+
- #10: `#attributes=` delegates to a setter for an unknown field preferentially, if the setter is present.
|
data/lib/opium/config.rb
CHANGED
data/lib/opium/extensions.rb
CHANGED
data/lib/opium/file.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'mimemagic'
|
2
|
+
|
3
|
+
module Opium
|
4
|
+
class File
|
5
|
+
include Opium::Model::Connectable
|
6
|
+
|
7
|
+
no_object_prefix!
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def model_name
|
11
|
+
@model_name ||= ActiveModel::Name.new( self )
|
12
|
+
end
|
13
|
+
|
14
|
+
def upload( file, options = {} )
|
15
|
+
request_options = build_request_options( file, options )
|
16
|
+
attributes = send( :http, :post, request_options ) do |request|
|
17
|
+
request.body = Faraday::UploadIO.new( file, request_options[:headers][:content_type] )
|
18
|
+
end
|
19
|
+
options[:sent_headers] ? attributes : new(attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_ruby( object )
|
23
|
+
return unless object
|
24
|
+
return object if object.is_a?( self )
|
25
|
+
if object.is_a?( Hash ) && (has_key_of_value( object, :__type, 'File' ) || has_keys( object, :url, :name ))
|
26
|
+
new( object )
|
27
|
+
else
|
28
|
+
fail ArgumentError, "could not convert #{ object.inspect } to an Opium::File"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_parse( object )
|
33
|
+
return unless object
|
34
|
+
fail ArgumentError, "could not convert #{ object.inspect } to a Parse File object" unless object.is_a?( self ) && object.name
|
35
|
+
{
|
36
|
+
__type: 'File',
|
37
|
+
name: object.name
|
38
|
+
}.with_indifferent_access
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Note that MimeMagic returns application/zip for the more recent MS Office file types,
|
44
|
+
# hence the extra check.
|
45
|
+
def build_request_options( file, options )
|
46
|
+
{}.tap do |result|
|
47
|
+
mime_type = options.fetch( :content_type, MimeMagic.by_magic(file) )
|
48
|
+
mime_type = MimeMagic.by_path(file) if mime_type == 'application/zip'
|
49
|
+
result[:id] = options[:original_filename] || ::File.basename( file )
|
50
|
+
result[:headers] = { content_type: mime_type.to_s }
|
51
|
+
result[:sent_headers] = options[:sent_headers] if options.key? :sent_headers
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_key_of_value( object, key, value )
|
56
|
+
(object[key] || object[key.to_s]) == value
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_keys( object, *keys )
|
60
|
+
object.keys.all? {|key| keys.include? key}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize( attributes = {} )
|
65
|
+
attributes.with_indifferent_access.each do |k, v|
|
66
|
+
send( :"#{k}=", v ) unless k == '__type'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete( options = {} )
|
71
|
+
fail "cannot delete #{ self.inspect }, as there is no name" unless self.name
|
72
|
+
self.class.with_heightened_privileges do
|
73
|
+
self.class.http_delete self.name, options
|
74
|
+
end.tap { self.freeze }
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :name, :url
|
78
|
+
|
79
|
+
def mime_type
|
80
|
+
@mime_type ||= MimeMagic.by_path( url ) if url
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect
|
84
|
+
"#<#{ self.class.model_name.name } name=#{ name.inspect } url=#{ url.inspect } mime_type=#{ (mime_type ? mime_type.type : nil).inspect }>"
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_parse
|
88
|
+
self.class.to_parse( self )
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
attr_writer :name, :url
|
94
|
+
end
|
95
|
+
end
|
@@ -13,9 +13,9 @@ module Opium
|
|
13
13
|
|
14
14
|
def attributes=(values)
|
15
15
|
sanitize_for_mass_assignment( rubyize_field_names( values ) ).each do |k, v|
|
16
|
-
field_info = self.class.fields[k]
|
17
|
-
if field_info.present?
|
18
|
-
send(
|
16
|
+
field_info, setter = self.class.fields[k], :"#{k}="
|
17
|
+
if field_info.present? || self.respond_to?( setter )
|
18
|
+
send( setter, v )
|
19
19
|
else
|
20
20
|
attributes[k] = v
|
21
21
|
end
|
@@ -24,13 +24,13 @@ module Opium
|
|
24
24
|
|
25
25
|
def attributes_to_parse( options = {} )
|
26
26
|
options[:except] ||= self.class.fields.values.select {|f| f.readonly? }.map {|f| f.name} if options[:not_readonly]
|
27
|
-
Hash[*self.as_json( options ).
|
27
|
+
Hash[*self.as_json( options ).flat_map {|k, v| [self.class.fields[k].name_to_parse, self.class.fields[k].type.to_parse(v)]}]
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def rubyize_field_names( hash )
|
33
|
-
|
33
|
+
hash.transform_keys {|k| self.class.ruby_canonical_field_names[k] || k}
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Opium
|
2
|
+
module Model
|
3
|
+
module Batchable
|
4
|
+
class Batch
|
5
|
+
MAX_BATCH_SIZE = 50
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.depth = 0
|
9
|
+
self.queue = []
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :depth, :queue, :owner
|
13
|
+
|
14
|
+
def dive
|
15
|
+
self.depth += 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def ascend
|
19
|
+
self.depth -= 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def enqueue( operation )
|
23
|
+
operation = Operation.new( operation ) if operation.is_a?( Hash )
|
24
|
+
self.queue.push( operation ) && operation
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute
|
28
|
+
if depth > 0
|
29
|
+
ascend
|
30
|
+
else
|
31
|
+
batches = to_parse
|
32
|
+
fail 'no batches to process' if batches.empty?
|
33
|
+
batches.each {|batch| owner.http_post( batch ) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_parse
|
38
|
+
queue.each_slice(MAX_BATCH_SIZE).map {|operations| { requests: operations.map(&:to_parse) } }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Opium
|
2
|
+
module Model
|
3
|
+
module Batchable
|
4
|
+
class Operation
|
5
|
+
def initialize( attributes = {} )
|
6
|
+
validate_key_present( attributes, :method )
|
7
|
+
validate_key_present( attributes, :path )
|
8
|
+
attributes.each do |key, value|
|
9
|
+
send( :"#{key}=", value )
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :method, :path, :body
|
14
|
+
|
15
|
+
def to_parse
|
16
|
+
{
|
17
|
+
method: method.to_s.upcase,
|
18
|
+
path: path
|
19
|
+
}.tap {|result| result[:body] = body if body }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate_key_present( attributes, key )
|
25
|
+
as_symbol, as_string = key.to_sym, key.to_s
|
26
|
+
fail ArgumentError, "missing an operation #{ key } parameter" unless attributes.key?( as_symbol ) || attributes.key?( as_string )
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_writer :method, :path, :body
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -87,6 +87,13 @@ module Opium
|
|
87
87
|
|
88
88
|
alias_method :has_heightened_privileges?, :requires_heightened_privileges?
|
89
89
|
|
90
|
+
def with_heightened_privileges(&block)
|
91
|
+
previous, @requires_heightened_privileges = @requires_heightened_privileges, true
|
92
|
+
block.call if block_given?
|
93
|
+
ensure
|
94
|
+
@requires_heightened_privileges = previous
|
95
|
+
end
|
96
|
+
|
90
97
|
private
|
91
98
|
|
92
99
|
def http( method, options, &block )
|
@@ -49,7 +49,7 @@ module Opium
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def default_attributes
|
52
|
-
|
52
|
+
fields.transform_values {|field| field.type.to_ruby field.default}.with_indifferent_access
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -81,6 +81,9 @@ module Opium
|
|
81
81
|
save!
|
82
82
|
end
|
83
83
|
|
84
|
+
alias_method :update, :update_attributes
|
85
|
+
alias_method :update!, :update_attributes!
|
86
|
+
|
84
87
|
def touch
|
85
88
|
save( validates: false )
|
86
89
|
end
|
@@ -116,20 +119,20 @@ module Opium
|
|
116
119
|
def create_or_update( options )
|
117
120
|
if options[:validates] == false || valid?
|
118
121
|
if new_record?
|
119
|
-
|
122
|
+
_create( options )
|
120
123
|
else
|
121
|
-
|
124
|
+
_update( options )
|
122
125
|
end
|
123
126
|
end.present?
|
124
127
|
end
|
125
128
|
|
126
|
-
def
|
129
|
+
def _create( options = {} )
|
127
130
|
self.attributes = attributes_or_headers( :post, :create, options ) do |headers|
|
128
131
|
self.class.http_post self.attributes_to_parse( except: [:id, :created_at, :updated_at] ), headers
|
129
132
|
end
|
130
133
|
end
|
131
134
|
|
132
|
-
def
|
135
|
+
def _update( options = {} )
|
133
136
|
self.attributes = attributes_or_headers( :put, :update, options ) do |headers|
|
134
137
|
self.class.http_put id, self.attributes_to_parse( only: changes.keys ), headers
|
135
138
|
end
|
@@ -6,8 +6,8 @@ module Opium
|
|
6
6
|
module ClassMethods
|
7
7
|
delegate :count, :total_count, to: :criteria
|
8
8
|
|
9
|
-
def all( constraints )
|
10
|
-
imbued_where( arrayize( constraints ), '$all' )
|
9
|
+
def all( constraints = nil )
|
10
|
+
constraints ? imbued_where( arrayize( constraints ), '$all' ) : criteria
|
11
11
|
end
|
12
12
|
|
13
13
|
alias_method :all_in, :all
|
data/lib/opium/model.rb
CHANGED
@@ -2,6 +2,7 @@ require 'active_support/concern'
|
|
2
2
|
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
|
+
require 'active_support/core_ext/hash/transform_values'
|
5
6
|
require 'active_support/inflector'
|
6
7
|
require 'opium/model/connectable'
|
7
8
|
require 'opium/model/persistable'
|
@@ -15,6 +16,7 @@ require 'opium/model/criteria'
|
|
15
16
|
require 'opium/model/scopable'
|
16
17
|
require 'opium/model/findable'
|
17
18
|
require 'opium/model/inheritable'
|
19
|
+
require 'opium/model/batchable'
|
18
20
|
require 'opium/model/kaminari'
|
19
21
|
|
20
22
|
module Opium
|
@@ -35,6 +37,7 @@ module Opium
|
|
35
37
|
include Scopable
|
36
38
|
include Findable
|
37
39
|
include Inheritable
|
40
|
+
include Batchable
|
38
41
|
end
|
39
42
|
|
40
43
|
def initialize( attributes = {} )
|
data/lib/opium/version.rb
CHANGED
data/lib/opium.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support'
|
4
|
+
require 'opium/version'
|
5
|
+
require 'opium/config'
|
6
|
+
require 'opium/extensions'
|
7
|
+
require 'opium/model'
|
8
|
+
require 'opium/user'
|
9
|
+
require 'opium/file'
|
10
|
+
require 'opium/railtie' if defined?( Rails )
|
data/opium.gemspec
CHANGED
@@ -6,6 +6,7 @@ require 'opium/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "opium"
|
8
8
|
spec.version = Opium::VERSION
|
9
|
+
spec.required_ruby_version = '>= 1.9.3'
|
9
10
|
spec.authors = ["Joshua Bowers"]
|
10
11
|
spec.email = ["joshua.bowers+code@gmail.com"]
|
11
12
|
spec.summary = %q{An Object Parse.com Mapping technology for defining models.}
|
@@ -37,4 +38,5 @@ Gem::Specification.new do |spec|
|
|
37
38
|
spec.add_dependency "activemodel", "~> 4.0"
|
38
39
|
spec.add_dependency "faraday", "~> 0.9"
|
39
40
|
spec.add_dependency "faraday_middleware", "~> 0.9"
|
41
|
+
spec.add_dependency "mimemagic", "~> 0.3"
|
40
42
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Symbol do
|
4
|
+
describe '.to_ruby' do
|
5
|
+
let(:converted) { described_class.to_ruby( param ) }
|
6
|
+
|
7
|
+
context 'with a string parameter' do
|
8
|
+
let(:param) { 'value' }
|
9
|
+
|
10
|
+
it { expect { converted }.to_not raise_exception }
|
11
|
+
it { expect( converted ).to be_a Symbol }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a symbol parameter' do
|
15
|
+
let(:param) { :value }
|
16
|
+
|
17
|
+
it { expect { converted }.to_not raise_exception }
|
18
|
+
it { expect( converted ).to be_a Symbol }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a nil parameter' do
|
22
|
+
let(:param) { nil }
|
23
|
+
|
24
|
+
it { expect { converted }.to_not raise_exception }
|
25
|
+
it { expect( converted ).to be_nil }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with anything else' do
|
29
|
+
let(:param) { 42 }
|
30
|
+
|
31
|
+
it { expect { converted }.to raise_exception }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.to_parse' do
|
36
|
+
let(:converted) { described_class.to_parse( param ) }
|
37
|
+
|
38
|
+
context 'with a symbol parameter' do
|
39
|
+
let(:param) { :value }
|
40
|
+
|
41
|
+
it { expect { converted }.to_not raise_exception }
|
42
|
+
it { expect( converted ).to be_a String }
|
43
|
+
it { expect( converted ).to eq 'value' }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#to_parse' do
|
48
|
+
subject { :value }
|
49
|
+
|
50
|
+
it { expect { subject.to_parse }.to_not raise_exception }
|
51
|
+
it { expect( subject.to_parse ).to be_a String }
|
52
|
+
it { expect( subject.to_parse ).to eq 'value' }
|
53
|
+
end
|
54
|
+
end
|