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