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,38 @@
1
+ module Opium
2
+ module Model
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ CALLBACKS = %w[before after around].map {|event| %w[save create update destroy].map {|action| :"#{event}_#{action}"}}.flatten +
7
+ %w[initialize find touch].map {|action| :"after_#{action}"} +
8
+ %w[before after].map {|event| :"#{event}_validation"}
9
+
10
+ included do
11
+ extend ActiveModel::Callbacks
12
+ include ActiveModel::Validations::Callbacks
13
+
14
+ define_model_callbacks :initialize, :find, :touch, only: :after
15
+ define_model_callbacks :save, :create, :update, :destroy
16
+
17
+ wrap_callbacks_around :save, :destroy, :touch, :find
18
+ wrap_callbacks_around :initialize, :create, :update, private: true
19
+ end
20
+
21
+ module ClassMethods
22
+ def wrap_callbacks_around( *methods )
23
+ options = methods.last.is_a?(::Hash) ? methods.pop : {}
24
+ methods.each do |method|
25
+ class_eval do
26
+ define_method method do |*args|
27
+ run_callbacks( method ) do
28
+ super( *args )
29
+ end
30
+ end
31
+ send( :private, method ) if options[:private]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,155 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+
4
+ module Opium
5
+ module Model
6
+ module Connectable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ end
11
+
12
+ class ParseError < StandardError
13
+ def initialize( code, error )
14
+ super( error )
15
+ @code = code
16
+ end
17
+
18
+ attr_reader :code
19
+ end
20
+
21
+ module ClassMethods
22
+ def connection
23
+ @@connection ||= Faraday.new( url: 'https://api.parse.com/1/' ) do |faraday|
24
+ faraday.request :multipart
25
+ faraday.request :url_encoded
26
+ faraday.response :logger if Opium.config.log_network_responses
27
+ faraday.response :json, content_type: /\bjson$/
28
+ faraday.headers[:x_parse_application_id] = Opium.config.app_id
29
+ faraday.adapter Faraday.default_adapter
30
+ end
31
+ end
32
+
33
+ def reset_connection!
34
+ @@connection = nil
35
+ end
36
+
37
+ def object_prefix
38
+ @object_prefix ||= 'classes'
39
+ end
40
+
41
+ # Parse doesn't route User objects through /classes/, instead treating them as a top-level class.
42
+ def no_object_prefix!
43
+ @object_prefix = ''
44
+ end
45
+
46
+ def as_resource( name, &block )
47
+ fail ArgumentError, 'no block given' unless block_given?
48
+ @masked_resource_name = name.to_s.freeze
49
+ block.call
50
+ ensure
51
+ @masked_resource_name = nil
52
+ end
53
+
54
+ def resource_name( resource_id = nil )
55
+ return @masked_resource_name if @masked_resource_name
56
+ @resource_name ||= Pathname.new( object_prefix ).join( map_name_to_resource( model_name ) )
57
+ ( resource_id ? @resource_name.join( resource_id ) : @resource_name ).to_s
58
+ end
59
+
60
+ def http_get( options = {} )
61
+ http( :get, options ) do |request|
62
+ options.fetch(:query, {}).each do |key, value|
63
+ request.params[key] = key.to_s == 'where' ? value.to_json : value
64
+ end
65
+ end
66
+ end
67
+
68
+ def http_post( data, options = {} )
69
+ http( :post, deeply_merge( options, content_type_json ), &infuse_request_with( data ) )
70
+ end
71
+
72
+ def http_put( id, data, options = {} )
73
+ http( :put, deeply_merge( options, content_type_json, id: id ), &infuse_request_with( data ) )
74
+ end
75
+
76
+ def http_delete( id, options = {} )
77
+ http( :delete, deeply_merge( options, id: id ) )
78
+ end
79
+
80
+ def requires_heightened_privileges!
81
+ @requires_heightened_privileges = true
82
+ end
83
+
84
+ def requires_heightened_privileges?
85
+ !@requires_heightened_privileges.nil?
86
+ end
87
+
88
+ private
89
+
90
+ def http( method, options, &block )
91
+ check_for_error( options ) do
92
+ if options[:sent_headers]
93
+ applier = apply_headers_to_request( method, options, &block )
94
+ request = connection.build_request( method )
95
+ applier.call( request )
96
+ request.headers
97
+ else
98
+ connection.send( method, resource_name( options[:id] ), &apply_headers_to_request( method, options, &block ) )
99
+ end
100
+ end
101
+ end
102
+
103
+ def deeply_merge( *args )
104
+ args.reduce {|a, e| a.deep_merge e }
105
+ end
106
+
107
+ def content_type_json
108
+ @content_type_json ||= { headers: { content_type: 'application/json' } }
109
+ end
110
+
111
+ def map_name_to_resource( model_name )
112
+ name = model_name.name.demodulize
113
+ @object_prefix.empty? ? name.tableize : name
114
+ end
115
+
116
+ def infuse_request_with( data )
117
+ lambda do |request|
118
+ request.body = data
119
+ request.body = request.body.to_json unless request.body.is_a?(String)
120
+ end
121
+ end
122
+
123
+ def apply_headers_to_request( method, options, &further_operations )
124
+ lambda do |request|
125
+ request.headers.update options[:headers] if options[:headers]
126
+
127
+ added_master_key =
128
+ unless request.headers[:x_parse_session_token]
129
+ if method != :get && requires_heightened_privileges? && Opium.config.master_key
130
+ request.headers[:x_parse_master_key] = Opium.config.master_key
131
+ end
132
+ end
133
+
134
+ request.headers[:x_parse_rest_api_key] = Opium.config.api_key unless added_master_key
135
+
136
+ further_operations.call( request ) if block_given?
137
+ end
138
+ end
139
+
140
+ def check_for_error( options = {}, &block )
141
+ fail ArgumentError, 'no block given' unless block_given?
142
+ result = yield
143
+ if options[:raw_response] || options[:sent_headers]
144
+ result
145
+ else
146
+ result = result.body
147
+ result = result.is_a?(Hash) ? result.with_indifferent_access : {}
148
+ fail ParseError.new( result[:code], result[:error] ) if result[:code] && result[:error]
149
+ result
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,123 @@
1
+ module Opium
2
+ module Model
3
+ class Criteria
4
+ include Opium::Model::Queryable::ClassMethods
5
+ include Enumerable
6
+
7
+ class_attribute :models
8
+ self.models = {}.with_indifferent_access
9
+
10
+ def initialize( model_name )
11
+ @model_name = model_name.respond_to?(:name) ? model_name.name : model_name
12
+ constraints[:count] = 1
13
+ end
14
+
15
+ attr_reader :model_name
16
+
17
+ def model
18
+ models[model_name] ||= model_name.constantize
19
+ end
20
+
21
+ def chain
22
+ Marshal.load( Marshal.dump( self ) )
23
+ end
24
+
25
+ def constraints
26
+ @constraints ||= {}.with_indifferent_access
27
+ end
28
+
29
+ def update_constraint( constraint, value )
30
+ chain.tap {|c| c.update_constraint!( constraint, value )}
31
+ end
32
+
33
+ def update_constraint!( constraint, value )
34
+ update_hash_value :constraints, constraint, value
35
+ end
36
+
37
+ def constraints?
38
+ !constraints.except(:count).empty?
39
+ end
40
+
41
+ def variables
42
+ @variables ||= {}.with_indifferent_access
43
+ end
44
+
45
+ def update_variable( variable, value )
46
+ chain.tap {|c| c.update_variable!( variable, value )}
47
+ end
48
+
49
+ def update_variable!( variable, value )
50
+ update_hash_value :variables, variable, value
51
+ end
52
+
53
+ def variables?
54
+ !variables.empty?
55
+ end
56
+
57
+ def empty?
58
+ count == 0
59
+ end
60
+
61
+ def criteria
62
+ self
63
+ end
64
+
65
+ def ==( other )
66
+ other.is_a?( self.class ) && self.model_name == other.model_name && self.constraints == other.constraints && self.variables == other.variables
67
+ end
68
+
69
+ def each(&block)
70
+ if !block_given?
71
+ to_enum(:each)
72
+ elsif cached? && @cache
73
+ @cache.each(&block)
74
+ else
75
+ response = self.model.http_get( query: self.constraints )
76
+ @cache = []
77
+ if response && response['results']
78
+ variables[:total_count] = response['count']
79
+ response['results'].each do |attributes|
80
+ model = self.model.new( attributes )
81
+ @cache << model if cached?
82
+ block.call model
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def inspect
89
+ inspected_constraints = constraints.map {|k, v| [k, v.inspect].join(': ')}.join(', ')
90
+ inspected_constraints.prepend ' ' if inspected_constraints.size > 0
91
+ "#<#{ self.class.name }<#{ model_name }>#{ inspected_constraints }>"
92
+ end
93
+
94
+ def to_parse
95
+ {}.with_indifferent_access.tap do |result|
96
+ result[:query] = { where: constraints[:where], className: model_name } if constraints[:where]
97
+ result[:key] = constraints[:keys] if constraints[:keys]
98
+ end
99
+ end
100
+
101
+ def uncache
102
+ super.tap do |criteria|
103
+ criteria.instance_variable_set(:@cache, nil)
104
+ end
105
+ end
106
+
107
+ def total_count
108
+ count && variables[:total_count]
109
+ end
110
+
111
+ private
112
+
113
+ def update_hash_value( hash_name, key, value )
114
+ hash = self.send( hash_name )
115
+ if hash[key].nil? || !value.is_a?(Hash)
116
+ hash[key] = value
117
+ elsif hash[key].is_a?(Hash) || value.is_a?(Hash)
118
+ hash[key].deep_merge!( value )
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,35 @@
1
+ module Opium
2
+ module Model
3
+ module Dirty
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include ActiveModel::Dirty
8
+ end
9
+
10
+ def initialize( attributes = {} )
11
+ super( attributes ).tap { self.send :clear_changes_information }
12
+ end
13
+
14
+ def save( options = {} )
15
+ super( options ).tap { self.send :changes_applied }
16
+ end
17
+
18
+ private
19
+
20
+ unless defined?(clear_changes_information)
21
+ def clear_changes_information
22
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
23
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
24
+ end
25
+ end
26
+
27
+ unless defined?(changes_applied)
28
+ def changes_applied
29
+ @previously_changed = changes
30
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ module Opium
2
+ module Model
3
+ class Field
4
+ def initialize(name, type, default, readonly, as)
5
+ self.name, self.type, self.default, self.readonly, self.as = name, type, default, readonly, as
6
+ end
7
+
8
+ attr_reader :name, :type, :readonly, :as
9
+
10
+ def default
11
+ if @default.respond_to? :call
12
+ @default.call
13
+ else
14
+ @default
15
+ end
16
+ end
17
+
18
+ def readonly?
19
+ self.readonly == true
20
+ end
21
+
22
+ def name_to_parse
23
+ @name_to_parse ||= (self.as || self.name).to_s.camelize(:lower)
24
+ end
25
+
26
+ private
27
+
28
+ attr_writer :name, :type, :default, :readonly, :as
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ require 'opium/model/field'
2
+
3
+ module Opium
4
+ module Model
5
+ module Fieldable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ field :id, type: String, readonly: true, as: :object_id
10
+ field :created_at, type: DateTime, readonly: true
11
+ field :updated_at, type: DateTime, readonly: true
12
+ end
13
+
14
+ module ClassMethods
15
+ def field( name, options = {} )
16
+ name = name.to_sym
17
+ fields[name] = Field.new( name, options[:type] || Object, options[:default], options[:readonly] || false, options[:as] )
18
+ ruby_canonical_field_names[name] = ruby_canonical_field_names[fields[name].name_to_parse] = name.to_s
19
+ parse_canonical_field_names[name] = parse_canonical_field_names[fields[name].name_to_parse] = fields[name].name_to_parse.to_s
20
+ class_eval do
21
+ define_attribute_methods [name]
22
+ define_method(name) do
23
+ self.attributes[name]
24
+ end
25
+ end
26
+ unless self.respond_to? "#{name}="
27
+ class_eval do
28
+ define_method("#{name}=") do |value|
29
+ converted = self.class.fields[name].type.to_ruby(value)
30
+ send( "#{name}_will_change!" ) unless self.attributes[name] == converted
31
+ self.attributes[name] = converted
32
+ end
33
+ send(:private, "#{name}=") if options[:readonly]
34
+ end
35
+ end
36
+ fields[name]
37
+ end
38
+
39
+ def fields
40
+ @fields ||= ActiveSupport::HashWithIndifferentAccess.new
41
+ end
42
+
43
+ def ruby_canonical_field_names
44
+ @ruby_canonical_field_names ||= ActiveSupport::HashWithIndifferentAccess.new
45
+ end
46
+
47
+ def parse_canonical_field_names
48
+ @parse_canonical_field_names ||= ActiveSupport::HashWithIndifferentAccess.new
49
+ end
50
+
51
+ def default_attributes
52
+ ActiveSupport::HashWithIndifferentAccess[ *fields.map {|key, field| [key, field.default]}.flatten ]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,20 @@
1
+ module Opium
2
+ module Model
3
+ module Findable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def find( id )
8
+ new self.http_get( id: id )
9
+ end
10
+
11
+ delegate \
12
+ :first,
13
+ :each,
14
+ :each_with_index,
15
+ :map,
16
+ to: :criteria
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ if defined?( Kaminari )
2
+ module Opium
3
+ module Model
4
+ module Kaminari
5
+ module Queryable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include ::Kaminari::PageScopeMethods
10
+
11
+ alias_method :offset, :skip
12
+
13
+ delegate :max_per_page, :default_per_page, :max_pages, to: :model_class
14
+
15
+ define_method ::Kaminari.config.page_method_name do |num|
16
+ cache.limit( limit_value ).offset( limit_value * ((num = num.to_i - 1) < 0 ? 0 : num) )
17
+ end
18
+
19
+ define_method :per do |num|
20
+ super( num ).cache
21
+ end
22
+ end
23
+
24
+ def limit_value
25
+ criteria.constraints.fetch(:limit, default_per_page)
26
+ end
27
+
28
+ def offset_value
29
+ criteria.constraints.fetch(:skip, 0)
30
+ end
31
+
32
+ def model_class
33
+ criteria.model
34
+ end
35
+
36
+ def entry_name
37
+ model_class.model_name.human.downcase
38
+ end
39
+ end
40
+ end
41
+
42
+ Opium::Model::Queryable::ClassMethods.send :include, Kaminari::Queryable
43
+ Opium::Model::Criteria.send :include, Kaminari::Queryable
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ module Opium
2
+ module Model
3
+ module Kaminari
4
+ module Scopable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include ::Kaminari::ConfigurationMethods
9
+ end
10
+ end
11
+ end
12
+
13
+ Opium::Model::Scopable.send :include, Kaminari::Scopable
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ if defined?( Kaminari )
2
+ require 'opium/model/kaminari/queryable'
3
+ require 'opium/model/kaminari/scopable'
4
+ end
@@ -0,0 +1,153 @@
1
+ module Opium
2
+ module Model
3
+ module Persistable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ end
8
+
9
+ class InvalidError < StandardError
10
+ end
11
+
12
+ module ClassMethods
13
+ def create( attributes = {} )
14
+ new( attributes ).tap {|model| model.save}
15
+ end
16
+
17
+ def create!( attributes = {} )
18
+ new( attributes ).tap {|model| model.save!}
19
+ end
20
+
21
+ def destroy_all( query = nil )
22
+
23
+ end
24
+
25
+ def delete_all( query = nil )
26
+
27
+ end
28
+
29
+ def added_headers
30
+ @added_headers ||= {}.with_indifferent_access
31
+ end
32
+
33
+ def add_header_to( methods, header, value, options = {} )
34
+ Array( methods ).each do |method|
35
+ added_headers[method] = { header: header, value: value, options: options }
36
+
37
+ added_headers[method][:options][:only] = Array( options[:only] )
38
+ added_headers[method][:options][:except] = Array( options[:except] )
39
+ end
40
+ nil
41
+ end
42
+
43
+ def get_header_for( method, context, owner = nil )
44
+ return {} unless added_headers[method]
45
+
46
+ eval_only = !added_headers[method][:options][:only].empty?
47
+ eval_except = !added_headers[method][:options][:except].empty?
48
+
49
+ within_only = added_headers[method][:options][:only].include?( context )
50
+ within_except = added_headers[method][:options][:except].include?( context )
51
+
52
+ value = added_headers[method][:value]
53
+ value = value.call( owner ) if owner && value.respond_to?( :call )
54
+
55
+ if value && ( ( !eval_only && !eval_except ) || ( eval_only && within_only ) || ( eval_except && !within_except ) )
56
+ { headers: { added_headers[method][:header] => value } }
57
+ else
58
+ {}
59
+ end
60
+ end
61
+ end
62
+
63
+ def save( options = {} )
64
+ create_or_update( options )
65
+ rescue => e
66
+ errors.add( :base, e.to_s )
67
+ false
68
+ end
69
+
70
+ def save!
71
+ create_or_update( validates: true ) || raise( InvalidError, 'failed to save, as model is invalid' )
72
+ end
73
+
74
+ def update_attributes( attributes = {} )
75
+ self.attributes = attributes
76
+ save
77
+ end
78
+
79
+ def update_attributes!( attributes = {} )
80
+ self.attributes = attributes
81
+ save!
82
+ end
83
+
84
+ def touch
85
+ save( validates: false )
86
+ end
87
+
88
+ def delete( options = {} )
89
+ self.tap do
90
+ attributes_or_headers( :delete, :delete, options ) do |headers|
91
+ self.class.http_delete id, headers unless new_record?
92
+ end
93
+ self.freeze
94
+ end
95
+ end
96
+ alias_method :destroy, :delete
97
+
98
+ def new_record?
99
+ self.id.nil?
100
+ end
101
+
102
+ def persisted?
103
+ !new_record? && !self.changed?
104
+ end
105
+
106
+ def pointer
107
+ @pointer ||= Pointer.new( model: self.class, id: id ) unless new_record?
108
+ end
109
+
110
+ def to_parse
111
+ pointer.to_parse
112
+ end
113
+
114
+ private
115
+
116
+ def create_or_update( options )
117
+ if options[:validates] == false || valid?
118
+ if new_record?
119
+ create( options )
120
+ else
121
+ update( options )
122
+ end
123
+ end.present?
124
+ end
125
+
126
+ def create( options = {} )
127
+ self.attributes = attributes_or_headers( :post, :create, options ) do |headers|
128
+ self.class.http_post self.attributes_to_parse( except: [:id, :created_at, :updated_at] ), headers
129
+ end
130
+ end
131
+
132
+ def update( options = {} )
133
+ self.attributes = attributes_or_headers( :put, :update, options ) do |headers|
134
+ self.class.http_put id, self.attributes_to_parse( only: changes.keys ), headers
135
+ end
136
+ end
137
+
138
+ def sent_headers
139
+ @_sent_headers || {}
140
+ end
141
+
142
+ def attributes_or_headers( method, action, options, &block )
143
+ result = block.call( options.deep_merge self.class.get_header_for( method, action, self ) )
144
+ if options[:sent_headers]
145
+ @_sent_headers = result
146
+ {}
147
+ else
148
+ result
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end