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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2b53d7cfe84ce144532da3f2cc3624b7c0fe6dfa
4
- data.tar.gz: 5a887bd81153f62b6efda115da56d13140818f0f
3
+ metadata.gz: 52cfc685abe291743976cf7e5df9bec31c212741
4
+ data.tar.gz: 346b5aa5edbd26292d06a10319b3b068cbb3c39f
5
5
  SHA512:
6
- metadata.gz: d7fa69828ec4e777c0532aa9ec4f65cb7eeda7d70c2af3f222a608882835ceeca8e43c5aae60d72e556b81cc4c865ccf16bb14b9c44031141bee893c94f84b1d
7
- data.tar.gz: ee6a29a7a0b4219a5e4876e1e83e0c9905a1a75c447e29d1b09090bad00a49fbb3f7afd4605fbe41d53027af4676674e5cfa01a7648d2a54baeffd066ba5f0a2
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.
@@ -7,6 +7,8 @@ module Rails
7
7
  case type.to_s
8
8
  when 'datetime'
9
9
  'DateTime'
10
+ when 'file'
11
+ 'Opium::File'
10
12
  else
11
13
  type.to_s.camelcase
12
14
  end
data/lib/opium/config.rb CHANGED
@@ -26,7 +26,7 @@ module Opium
26
26
 
27
27
  def load_yaml( path, environment = nil )
28
28
  env = environment ? environment.to_s : env_name
29
- YAML.load(ERB.new(File.new(path).read).result)[env]
29
+ YAML.load(ERB.new(::File.new(path).read).result)[env]
30
30
  end
31
31
 
32
32
  def env_name
@@ -0,0 +1,13 @@
1
+ class ::Symbol
2
+ class << self
3
+ def to_ruby( other )
4
+ other.to_sym if other
5
+ end
6
+
7
+ def to_parse( other )
8
+ other.to_parse
9
+ end
10
+ end
11
+
12
+ alias_method :to_parse, :to_s
13
+ end
@@ -13,4 +13,5 @@ require 'opium/extensions/date_time'
13
13
  require 'opium/extensions/date'
14
14
  require 'opium/extensions/time'
15
15
  require 'opium/extensions/regexp'
16
+ require 'opium/extensions/symbol'
16
17
  require 'opium/extensions/pointer'
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( "#{k}=", v )
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 ).map {|k, v| [self.class.fields[k].name_to_parse, v.to_parse]}.flatten]
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
- Hash[*hash.map {|k, v| [self.class.ruby_canonical_field_names[k] || k, v]}.flatten]
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
@@ -0,0 +1,10 @@
1
+ require 'opium/model/batchable/operation'
2
+ require 'opium/model/batchable/batch'
3
+
4
+ module Opium
5
+ module Model
6
+ module Batchable
7
+ extend ActiveSupport::Concern
8
+ end
9
+ end
10
+ 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
- ActiveSupport::HashWithIndifferentAccess[ *fields.map {|key, field| [key, field.default]}.flatten ]
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
- create( options )
122
+ _create( options )
120
123
  else
121
- update( options )
124
+ _update( options )
122
125
  end
123
126
  end.present?
124
127
  end
125
128
 
126
- def create( options = {} )
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 update( options = {} )
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
@@ -1,3 +1,3 @@
1
1
  module Opium
2
- VERSION = "1.0.3"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/opium.rb CHANGED
@@ -1,9 +1,10 @@
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/railtie" if defined?( Rails )
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