opium 1.0.0.beta

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +24 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +71 -0
  9. data/Rakefile +10 -0
  10. data/lib/generators/opium/config_generator.rb +15 -0
  11. data/lib/generators/opium/model_generator.rb +33 -0
  12. data/lib/generators/opium/templates/config.yml +27 -0
  13. data/lib/generators/opium/templates/model.rb +10 -0
  14. data/lib/opium/config.rb +44 -0
  15. data/lib/opium/extensions/array.rb +10 -0
  16. data/lib/opium/extensions/boolean.rb +13 -0
  17. data/lib/opium/extensions/date.rb +18 -0
  18. data/lib/opium/extensions/date_time.rb +18 -0
  19. data/lib/opium/extensions/false_class.rb +7 -0
  20. data/lib/opium/extensions/float.rb +13 -0
  21. data/lib/opium/extensions/geo_point.rb +37 -0
  22. data/lib/opium/extensions/hash.rb +43 -0
  23. data/lib/opium/extensions/integer.rb +13 -0
  24. data/lib/opium/extensions/numeric.rb +7 -0
  25. data/lib/opium/extensions/object.rb +15 -0
  26. data/lib/opium/extensions/pointer.rb +20 -0
  27. data/lib/opium/extensions/regexp.rb +12 -0
  28. data/lib/opium/extensions/string.rb +20 -0
  29. data/lib/opium/extensions/time.rb +19 -0
  30. data/lib/opium/extensions/true_class.rb +7 -0
  31. data/lib/opium/extensions.rb +16 -0
  32. data/lib/opium/model/attributable.rb +37 -0
  33. data/lib/opium/model/callbacks.rb +38 -0
  34. data/lib/opium/model/connectable.rb +155 -0
  35. data/lib/opium/model/criteria.rb +123 -0
  36. data/lib/opium/model/dirty.rb +35 -0
  37. data/lib/opium/model/field.rb +31 -0
  38. data/lib/opium/model/fieldable.rb +57 -0
  39. data/lib/opium/model/findable.rb +20 -0
  40. data/lib/opium/model/kaminari/queryable.rb +46 -0
  41. data/lib/opium/model/kaminari/scopable.rb +15 -0
  42. data/lib/opium/model/kaminari.rb +4 -0
  43. data/lib/opium/model/persistable.rb +153 -0
  44. data/lib/opium/model/queryable.rb +150 -0
  45. data/lib/opium/model/scopable.rb +58 -0
  46. data/lib/opium/model/serialization.rb +13 -0
  47. data/lib/opium/model.rb +47 -0
  48. data/lib/opium/railtie.rb +14 -0
  49. data/lib/opium/user.rb +44 -0
  50. data/lib/opium/version.rb +3 -0
  51. data/lib/opium.rb +9 -0
  52. data/opium.gemspec +40 -0
  53. data/spec/opium/config/opium.yml +5 -0
  54. data/spec/opium/config_spec.rb +56 -0
  55. data/spec/opium/extensions/array_spec.rb +34 -0
  56. data/spec/opium/extensions/boolean_spec.rb +28 -0
  57. data/spec/opium/extensions/date_spec.rb +55 -0
  58. data/spec/opium/extensions/date_time_spec.rb +55 -0
  59. data/spec/opium/extensions/float_spec.rb +42 -0
  60. data/spec/opium/extensions/geo_point_spec.rb +55 -0
  61. data/spec/opium/extensions/hash_spec.rb +76 -0
  62. data/spec/opium/extensions/integer_spec.rb +42 -0
  63. data/spec/opium/extensions/object_spec.rb +24 -0
  64. data/spec/opium/extensions/pointer_spec.rb +28 -0
  65. data/spec/opium/extensions/regexp_spec.rb +23 -0
  66. data/spec/opium/extensions/string_spec.rb +65 -0
  67. data/spec/opium/extensions/time_spec.rb +55 -0
  68. data/spec/opium/model/attributable_spec.rb +45 -0
  69. data/spec/opium/model/callbacks_spec.rb +59 -0
  70. data/spec/opium/model/connectable_spec.rb +218 -0
  71. data/spec/opium/model/criteria_spec.rb +285 -0
  72. data/spec/opium/model/dirty_spec.rb +39 -0
  73. data/spec/opium/model/fieldable_spec.rb +133 -0
  74. data/spec/opium/model/findable_spec.rb +57 -0
  75. data/spec/opium/model/kaminari/queryable_spec.rb +22 -0
  76. data/spec/opium/model/kaminari/scopable_spec.rb +20 -0
  77. data/spec/opium/model/kaminari_spec.rb +104 -0
  78. data/spec/opium/model/persistable_spec.rb +367 -0
  79. data/spec/opium/model/queryable_spec.rb +338 -0
  80. data/spec/opium/model/scopable_spec.rb +115 -0
  81. data/spec/opium/model/serialization_spec.rb +51 -0
  82. data/spec/opium/model_spec.rb +49 -0
  83. data/spec/opium/user_spec.rb +195 -0
  84. data/spec/opium_spec.rb +5 -0
  85. data/spec/spec_helper.rb +25 -0
  86. metadata +400 -0
@@ -0,0 +1,150 @@
1
+ module Opium
2
+ module Model
3
+ module Queryable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ delegate :count, :total_count, to: :criteria
8
+
9
+ def all( constraints )
10
+ imbued_where( arrayize( constraints ), '$all' )
11
+ end
12
+
13
+ alias_method :all_in, :all
14
+
15
+ def between( constraints )
16
+ start = constraints.map {|key, range| [key, range.begin]}
17
+ inclusive = constraints.reject {|_, range| range.exclude_end?}.map {|key, range| [key, range.end]}
18
+ exclusive = constraints.select {|_, range| range.exclude_end?}.map {|key, range| [key, range.end]}
19
+ gte( start ).lte( inclusive ).lt( exclusive )
20
+ end
21
+
22
+ def exists( constraints )
23
+ imbued_where( constraints.map {|key, value| [key, value.to_bool] }, '$exists' )
24
+ end
25
+
26
+ def gt( constraints )
27
+ imbued_where( constraints, '$gt' )
28
+ end
29
+
30
+ def gte( constraints )
31
+ imbued_where( constraints, '$gte' )
32
+ end
33
+
34
+ def lt( constraints )
35
+ imbued_where( constraints, '$lt' )
36
+ end
37
+
38
+ def lte( constraints )
39
+ imbued_where( constraints, '$lte' )
40
+ end
41
+
42
+ def in( constraints )
43
+ imbued_where( arrayize( constraints ), '$in' )
44
+ end
45
+
46
+ alias_method :any_in, :in
47
+
48
+ def nin( constraints )
49
+ imbued_where( arrayize( constraints ), '$nin' )
50
+ end
51
+
52
+ def ne( constraints )
53
+ imbued_where( constraints, '$ne' )
54
+ end
55
+
56
+ def or( *subqueries )
57
+ where( '$or' => subqueries )
58
+ end
59
+
60
+ def select( constraints )
61
+ imbued_where( constraints, '$select' )
62
+ end
63
+
64
+ def dont_select( constraints )
65
+ imbued_where( constraints, '$dontSelect' )
66
+ end
67
+
68
+ def keys( *field_names )
69
+ validate_fields_exist( field_names )
70
+ criteria.update_constraint( :keys, field_names.map(&method(:translate_name)).join(',') )
71
+ end
72
+
73
+ # Should be noted that pluck is an immediate query execution, so doesn't play well with further chainable criteria
74
+ def pluck( field_name )
75
+
76
+ end
77
+
78
+ def order( options )
79
+ validate_fields_exist( options )
80
+ previous = criteria.constraints[:order]
81
+ ordering = (
82
+ [previous].compact + options.map {|key, value| (['-', 'desc', '-1'].include?( value.to_s ) ? '-' : '' ) + translate_name( key.to_s ) }
83
+ ).join(',')
84
+ criteria.update_constraint( :order, ordering )
85
+ end
86
+
87
+ def limit( value )
88
+ criteria.update_constraint( :limit, value )
89
+ end
90
+
91
+ def skip( value )
92
+ criteria.update_constraint( :skip, value )
93
+ end
94
+
95
+ def where( constraints )
96
+ validate_fields_exist( constraints )
97
+ criteria.update_constraint( :where, translate_to_parse( constraints ) )
98
+ end
99
+
100
+ alias_method :and, :where
101
+
102
+ def cache
103
+ criteria.update_variable( :cache, true )
104
+ end
105
+
106
+ def uncache
107
+ criteria.update_variable( :cache, false )
108
+ end
109
+
110
+ def cached?
111
+ criteria.variables[:cache]
112
+ end
113
+
114
+ private
115
+
116
+ def model
117
+ self
118
+ end
119
+
120
+ def validate_fields_exist( field_names )
121
+ field_names = field_names.keys if field_names.respond_to? :keys
122
+ unless field_names.all? {|field_name| model.fields.key?( field_name ) || field_name =~ /^\$/ }
123
+ not_fields = field_names.reject {|field_name| model.fields.key? field_name }
124
+ raise ArgumentError, "#{not_fields.join(', ')} #{not_fields.length > 1 ? 'are not fields' : 'is not a field'} on this model"
125
+ end
126
+ end
127
+
128
+ def translate_name( field_name )
129
+ field_name =~ /^\$/ ? field_name : model.parse_canonical_field_names[ field_name ]
130
+ end
131
+
132
+ def translate_to_parse( constraints )
133
+ Hash[ *constraints.flat_map {|key, value| [translate_name( key ), value.to_parse]} ]
134
+ end
135
+
136
+ def arrayize( constraints )
137
+ constraints.map {|key, value| [key, value.to_a]}
138
+ end
139
+
140
+ def imbued_where( constraints, operator )
141
+ where( imbue_field_constraints_with_operator( constraints, operator ) )
142
+ end
143
+
144
+ def imbue_field_constraints_with_operator( constraints, operator )
145
+ Hash[ *constraints.flat_map {|key, value| [key, { operator => value }]} ]
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,58 @@
1
+ module Opium
2
+ module Model
3
+ module Scopable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ end
8
+
9
+ module ClassMethods
10
+ def criteria
11
+ @_unscoped ? blank_criteria : default_scope
12
+ end
13
+
14
+ def scope( scope_name, criteria = nil, &block )
15
+ class_eval do
16
+ method_body = if block_given? || criteria.is_a?(Proc)
17
+ block || criteria
18
+ elsif criteria.nil?
19
+ raise ArgumentError, "Criteria cannot be nil if no block is provided."
20
+ else
21
+ -> { criteria }
22
+ end
23
+ define_singleton_method( scope_name, method_body )
24
+ end
25
+ end
26
+
27
+ def default_scope( criteria = nil, &block )
28
+ @default_scope = block || criteria if block_given? || criteria
29
+ s = @default_scope || blank_criteria
30
+ s.is_a?( Proc ) ? unscoped { s.call } : s
31
+ end
32
+
33
+ def scoped
34
+
35
+ end
36
+
37
+ def unscoped
38
+ if block_given?
39
+ @_unscoped = true
40
+ yield
41
+ else
42
+ blank_criteria
43
+ end
44
+ ensure
45
+ @_unscoped = false
46
+ end
47
+
48
+ def blank_criteria
49
+ Criteria.new( self.model_name )
50
+ end
51
+
52
+ def with_scope( criteria )
53
+
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,13 @@
1
+ module Opium
2
+ module Model
3
+ module Serialization
4
+ extend ActiveSupport::Concern
5
+
6
+ include ActiveModel::Serializers::JSON
7
+
8
+ included do
9
+ self.include_root_in_json = false
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/string'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+ require 'active_support/core_ext/hash/deep_merge'
5
+ require 'active_support/inflector'
6
+ require 'opium/model/connectable'
7
+ require 'opium/model/persistable'
8
+ require 'opium/model/callbacks'
9
+ require 'opium/model/serialization'
10
+ require 'opium/model/dirty'
11
+ require 'opium/model/fieldable'
12
+ require 'opium/model/attributable'
13
+ require 'opium/model/queryable'
14
+ require 'opium/model/criteria'
15
+ require 'opium/model/scopable'
16
+ require 'opium/model/findable'
17
+ require 'opium/model/kaminari'
18
+
19
+ module Opium
20
+ module Model
21
+ extend ActiveSupport::Concern
22
+
23
+ include ActiveModel::Model
24
+
25
+ included do
26
+ include Connectable
27
+ include Persistable
28
+ include Dirty
29
+ include Fieldable
30
+ include Serialization
31
+ include Attributable
32
+ include Queryable
33
+ include Callbacks
34
+ include Scopable
35
+ include Findable
36
+ end
37
+
38
+ def initialize( attributes = {} )
39
+ self.attributes = attributes
40
+ end
41
+
42
+ def inspect
43
+ inspected_fields = self.attributes.map {|k, v| [k, v.inspect].join(': ')}.join(', ')
44
+ "#<#{self.class.model_name} #{inspected_fields}>"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ module Opium
2
+ class Railtie < Rails::Railtie
3
+ config.app_generators.orm :opium, migration: false
4
+
5
+ config.opium = ::Opium.config
6
+
7
+ initializer 'opium.load-config' do
8
+ config_file = Rails.root.join( 'config', 'opium.yml' )
9
+ if config_file.file?
10
+ ::Opium.load!( config_file )
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/opium/user.rb ADDED
@@ -0,0 +1,44 @@
1
+ module Opium
2
+ class User
3
+ include Opium::Model
4
+
5
+ field :username, type: String
6
+ field :password, type: String
7
+ field :email, type: String
8
+ field :email_verified, type: Boolean
9
+ field :session_token, type: String, readonly: true
10
+
11
+ no_object_prefix!
12
+ requires_heightened_privileges!
13
+ add_header_to [:put, :delete], :x_parse_session_token, :session_token.to_proc
14
+
15
+ class << self
16
+ # Note that this will eat any ParseErrors which get raised, and not perform any logging.
17
+ def authenticate( username, password )
18
+ authenticate!( username, password )
19
+ rescue Opium::Model::Connectable::ParseError => e
20
+ nil
21
+ end
22
+
23
+ def authenticate!( username, password )
24
+ new( as_resource('login') { http_get query: { username: username, password: password } } )
25
+ end
26
+
27
+ def find_by_session_token( token )
28
+ new( http_get id: 'me', headers: { x_parse_session_token: token } )
29
+ end
30
+ end
31
+
32
+ def reset_password
33
+ reset_password!
34
+ rescue => e
35
+ self.errors.add( :email, e.to_s )
36
+ false
37
+ end
38
+
39
+ def reset_password!
40
+ fail KeyError, 'an email address is required to reset password' unless email
41
+ self.class.as_resource('requestPasswordReset') { self.class.http_post data: email }.empty?
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Opium
2
+ VERSION = "1.0.0.beta"
3
+ end
data/lib/opium.rb ADDED
@@ -0,0 +1,9 @@
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 )
data/opium.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'opium/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "opium"
8
+ spec.version = Opium::VERSION
9
+ spec.authors = ["Joshua Bowers"]
10
+ spec.email = ["joshua.bowers+code@gmail.com"]
11
+ spec.summary = %q{An Object Parse.com Mapping technology for defining models.}
12
+ spec.description = %q{Provides an intuitive, Mongoid-inspired mapping layer between your application's object space and Parse.'}
13
+ spec.homepage = "https://github.com/joshuabowers/opium"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "rspec-nc"
25
+ spec.add_development_dependency "rspec-its"
26
+ spec.add_development_dependency "webmock"
27
+ spec.add_development_dependency "guard"
28
+ spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "pry-remote"
31
+ spec.add_development_dependency "pry-nav"
32
+ spec.add_development_dependency "coveralls"
33
+
34
+ spec.add_development_dependency "rails"
35
+ spec.add_development_dependency "kaminari"
36
+
37
+ spec.add_dependency "activemodel", "~> 4.0"
38
+ spec.add_dependency "faraday", "~> 0.9"
39
+ spec.add_dependency "faraday_middleware", "~> 0.9"
40
+ end
@@ -0,0 +1,5 @@
1
+ test:
2
+ app_id: abcd1234
3
+ api_key: efgh5678
4
+ master_key: 9012ijkl
5
+ log_network_responses: true
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium do
4
+ it { should respond_to(:configure, :config) }
5
+ it { should respond_to(:load!).with(2).arguments }
6
+ it { should respond_to(:reset) }
7
+
8
+ describe ':configure' do
9
+ it do
10
+ expect {|b| Opium.configure(&b) }.to yield_with_args
11
+ end
12
+ end
13
+
14
+ describe ':config' do
15
+ subject { Opium.config }
16
+
17
+ it { should_not be_nil }
18
+ it { should be_an( Opium::Config ) }
19
+ end
20
+
21
+ describe ':reset' do
22
+ it 'should put all changed settings back to their defaults' do
23
+ expect { described_class.config.log_network_responses = true }.to change( described_class.config, :log_network_responses ).from( false ).to( true )
24
+ described_class.reset
25
+ described_class.config.log_network_responses.should == false
26
+ end
27
+ end
28
+
29
+ describe ':load!' do
30
+ let(:file) { File.join( File.dirname( __FILE__ ), 'config', 'opium.yml' ) }
31
+
32
+ before do
33
+ described_class.load!( file, :test )
34
+ end
35
+
36
+ after do
37
+ described_class.reset
38
+ end
39
+
40
+ it { subject.config.app_id.should == 'abcd1234' }
41
+ it { subject.config.api_key.should == 'efgh5678' }
42
+ it { subject.config.master_key.should == '9012ijkl' }
43
+ it { subject.config.log_network_responses.should == true }
44
+ end
45
+
46
+ describe Opium::Config do
47
+ it { should respond_to( :app_id, :api_key, :master_key, :log_network_responses ) }
48
+
49
+ describe 'defaults' do
50
+ its(:app_id) { should == 'PARSE_APP_ID' }
51
+ its(:api_key) { should == 'PARSE_API_KEY' }
52
+ its(:master_key) { should == 'PARSE_MASTER_KEY' }
53
+ its(:log_network_responses) { should == false }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ subject { Array }
5
+
6
+ describe 'instance' do
7
+ describe ':to_parse' do
8
+ subject { [GeoPoint.new( latitude: 33.33, longitude: -117.117 )] }
9
+
10
+ it 'should call :to_parse on each element' do
11
+ subject.to_parse.tap do |result|
12
+ result.should =~ [ { '__type' => 'GeoPoint', 'latitude' => 33.33, 'longitude' => -117.117 } ]
13
+ end
14
+ end
15
+ end
16
+
17
+ describe ':to_geo_point' do
18
+ describe 'with two values' do
19
+ subject { [33.33, -117.117] }
20
+
21
+ it { subject.to_geo_point.should be_a_kind_of(GeoPoint) }
22
+ it 'should have the expected latitude and longitude' do
23
+ subject.to_geo_point.latitude.should == 33.33
24
+ subject.to_geo_point.longitude.should == -117.117
25
+ end
26
+ end
27
+
28
+ describe 'with more or fewer than two values' do
29
+ it { expect { [33.33].to_geo_point }.to raise_exception }
30
+ it { expect { [33.33, -117.117, :deadbeef].to_geo_point }.to raise_exception }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Boolean do
4
+ subject { Boolean }
5
+ it { should respond_to(:to_ruby).with(1).argument }
6
+ it { should respond_to(:to_parse).with(1).argument }
7
+
8
+ it ":to_ruby(object) should convert object to a boolean" do
9
+ ["true", "false", true, false, 1.0, 1, 0.0, 0].each do |value|
10
+ subject.to_ruby(value).should be_a_kind_of(Boolean)
11
+ subject.to_ruby(value).should == value.to_bool
12
+ end
13
+ end
14
+
15
+ it ":to_parse(object) should convert object to a boolean" do
16
+ ["true", "false", true, false, 1.0, 1, 0.0, 0].each do |value|
17
+ subject.to_parse(value).should be_a_kind_of(Boolean)
18
+ subject.to_parse(value).should == value.to_bool
19
+ end
20
+ end
21
+
22
+ describe "instance" do
23
+ subject { Class.new { include Boolean }.new }
24
+ it { should respond_to(:to_ruby, :to_parse).with(0).arguments }
25
+ its(:to_ruby) { should == subject }
26
+ its(:to_parse) { should == subject }
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Date do
4
+ subject { Date }
5
+ it { should respond_to(:to_parse, :to_ruby).with(1).argument }
6
+
7
+ describe ":to_ruby" do
8
+ describe "with a Hash containing '__type: Date'" do
9
+ let(:object) { { '__type' => 'Date', 'iso' => Date.today.iso8601 } }
10
+ it { subject.to_ruby(object).should be_a_kind_of( Date ) }
11
+ end
12
+
13
+ describe "with a Hash without a '__type' key" do
14
+ let(:object) { { 'iso' => Date.today.iso8601 } }
15
+ it { expect { subject.to_ruby(object) }.to raise_exception }
16
+ end
17
+
18
+ describe "with an object which responds to :to_date" do
19
+ let(:objects) { [ Date.today.iso8601, DateTime.now, Time.now, Date.today ] }
20
+ it do
21
+ objects.each do |object|
22
+ subject.to_ruby(object).should be_a_kind_of( Date )
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ":to_parse(object)" do
29
+ let(:objects) { [ Date.today.iso8601, DateTime.now, Time.now, Date.today ] }
30
+ it "should ensure that the object is a datetime" do
31
+ objects.each do |object|
32
+ object.should_receive :to_date
33
+ subject.to_parse(object)
34
+ end
35
+ end
36
+
37
+ it "should make a parse object hash" do
38
+ objects.each do |object|
39
+ result = subject.to_parse(object)
40
+ result.should be_a_kind_of(Hash)
41
+ result.keys.should == ['__type', 'iso']
42
+ result['__type'].should == 'Date'
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "instance" do
48
+ subject { Date.today }
49
+ let(:result) { subject.to_parse }
50
+ it { result.should be_a_kind_of(Hash) }
51
+ it { result.keys.should == ['__type', 'iso'] }
52
+ it { result['__type'].should == 'Date' }
53
+ it { result['iso'].should == subject.iso8601 }
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe DateTime do
4
+ subject { DateTime }
5
+ it { should respond_to(:to_parse, :to_ruby).with(1).argument }
6
+
7
+ describe ":to_ruby" do
8
+ describe "with a Hash containing '__type: DateTime'" do
9
+ let(:object) { { '__type' => 'Date', 'iso' => DateTime.now.iso8601 } }
10
+ it { subject.to_ruby(object).should be_a_kind_of( DateTime ) }
11
+ end
12
+
13
+ describe "with a Hash without a '__type' key" do
14
+ let(:object) { { 'iso' => DateTime.now.iso8601 } }
15
+ it { expect { subject.to_ruby(object) }.to raise_exception }
16
+ end
17
+
18
+ describe "with an object which responds to :to_datetime" do
19
+ let(:objects) { [ DateTime.now.iso8601, DateTime.now, Time.now, Date.today ] }
20
+ it do
21
+ objects.each do |object|
22
+ subject.to_ruby(object).should be_a_kind_of( DateTime )
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ":to_parse(object)" do
29
+ let(:objects) { [ DateTime.now.iso8601, DateTime.now, Time.now, Date.today ] }
30
+ it "should ensure that the object is a datetime" do
31
+ objects.each do |object|
32
+ object.should_receive :to_datetime
33
+ subject.to_parse(object)
34
+ end
35
+ end
36
+
37
+ it "should make a parse object hash" do
38
+ objects.each do |object|
39
+ result = subject.to_parse(object)
40
+ result.should be_a_kind_of(Hash)
41
+ result.keys.should == ['__type', 'iso']
42
+ result['__type'].should == 'Date'
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "instance" do
48
+ subject { DateTime.now }
49
+ let(:result) { subject.to_parse }
50
+ it { result.should be_a_kind_of(Hash) }
51
+ it { result.keys.should == ['__type', 'iso'] }
52
+ it { result['__type'].should == 'Date' }
53
+ it { result['iso'].should == subject.iso8601 }
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe ::Float do
4
+ subject { Float }
5
+ it { should respond_to(:to_ruby).with(1).argument }
6
+ it { should respond_to(:to_parse).with(1).argument }
7
+
8
+ it ":to_ruby(object) should convert object to a float" do
9
+ {nil => nil, :foo => :foo.to_s.to_f}.each do |value, expected|
10
+ subject.to_ruby(value).should be_a_kind_of(expected.class)
11
+ subject.to_ruby(value).should == expected
12
+ end
13
+ ["foo", 42.0, 42, Time.now].each do |value|
14
+ subject.to_ruby(value).should be_a_kind_of(Float)
15
+ subject.to_ruby(value).should == value.to_f
16
+ end
17
+ end
18
+
19
+ it ":to_parse(object) should convert object to a float" do
20
+ {nil => nil, :foo => :foo.to_s.to_f}.each do |value, expected|
21
+ subject.to_parse(value).should be_a_kind_of(expected.class)
22
+ subject.to_parse(value).should == expected
23
+ end
24
+ ["foo", 42.0, 42, Time.now].each do |value|
25
+ subject.to_parse(value).should be_a_kind_of(Float)
26
+ subject.to_parse(value).should == value.to_f
27
+ end
28
+ end
29
+
30
+ describe "instance" do
31
+ subject { 42.0 }
32
+ it { should respond_to(:to_ruby, :to_parse, :to_bool).with(0).arguments }
33
+ its(:to_ruby) { should == subject }
34
+ its(:to_parse) { should == subject }
35
+
36
+ it "should convert 1.0 and 0.0 to true and false, all others raise" do
37
+ 1.0.to_bool.should == true
38
+ 0.0.to_bool.should == false
39
+ expect { subject.to_bool }.to raise_exception
40
+ end
41
+ end
42
+ end