opium 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|