flexirest 1.6.7 → 1.6.8
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 +4 -4
- data/CHANGELOG.md +54 -48
- data/README.md +0 -0
- data/docs/validation.md +30 -1
- data/flexirest.gemspec +1 -0
- data/lib/flexirest.rb +5 -0
- data/lib/flexirest/base.rb +3 -217
- data/lib/flexirest/base_without_validation.rb +219 -0
- data/lib/flexirest/version.rb +1 -1
- data/spec/lib/activemodel_validations_spec.rb +44 -0
- data/spec/lib/associations_spec.rb +5 -5
- data/spec/lib/base_spec.rb +3 -505
- data/spec/lib/base_without_validation_spec.rb +517 -0
- metadata +21 -2
@@ -0,0 +1,219 @@
|
|
1
|
+
module Flexirest
|
2
|
+
class BaseWithoutValidation
|
3
|
+
include Mapping
|
4
|
+
include Configuration
|
5
|
+
include Callbacks
|
6
|
+
include Caching
|
7
|
+
include Recording
|
8
|
+
include AttributeParsing
|
9
|
+
include Associations
|
10
|
+
|
11
|
+
attr_accessor :_status
|
12
|
+
attr_accessor :_etag
|
13
|
+
attr_accessor :_headers
|
14
|
+
|
15
|
+
instance_methods.each do |m|
|
16
|
+
next unless %w{display presence load require hash untrust trust freeze method enable_warnings with_warnings suppress capture silence quietly debugger breakpoint}.map(&:to_sym).include? m
|
17
|
+
undef_method m
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(attrs={})
|
21
|
+
@attributes = {}
|
22
|
+
@dirty_attributes = Hash.new
|
23
|
+
|
24
|
+
raise Exception.new("Cannot instantiate Base class") if self.class == Flexirest::BaseWithoutValidation
|
25
|
+
|
26
|
+
attrs.each do |attribute_name, attribute_value|
|
27
|
+
attribute_name = attribute_name.to_sym
|
28
|
+
@attributes[attribute_name] = parse_date?(attribute_name) ? parse_attribute_value(attribute_value) : attribute_value
|
29
|
+
@dirty_attributes[attribute_name] = [nil, attribute_value]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def _clean!
|
34
|
+
@dirty_attributes = Hash.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def _attributes
|
38
|
+
@attributes
|
39
|
+
end
|
40
|
+
|
41
|
+
def _copy_from(result)
|
42
|
+
@attributes = result._attributes
|
43
|
+
@_status = result._status
|
44
|
+
end
|
45
|
+
|
46
|
+
def dirty?
|
47
|
+
@dirty_attributes.size > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def changed?
|
51
|
+
dirty?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns an array of changed fields
|
55
|
+
def changed
|
56
|
+
@dirty_attributes.keys
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns hash of old and new vaules for each changed field
|
60
|
+
def changes
|
61
|
+
@dirty_attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
def self._request(request, method = :get, params = nil, options = {})
|
65
|
+
prepare_direct_request(request, method, options).call(params)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self._plain_request(request, method = :get, params = nil, options = {})
|
69
|
+
prepare_direct_request(request, method, options.merge(plain:true)).call(params)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self._lazy_request(request, method = :get, params = nil, options = {})
|
73
|
+
Flexirest::LazyLoader.new(prepare_direct_request(request, method, options), params)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.prepare_direct_request(request, method = :get, options={})
|
77
|
+
unless request.is_a? Flexirest::Request
|
78
|
+
options[:plain] ||= false
|
79
|
+
options[:direct] ||= true
|
80
|
+
request = Flexirest::Request.new({ url: request, method: method, options: options }, self)
|
81
|
+
end
|
82
|
+
request
|
83
|
+
end
|
84
|
+
|
85
|
+
def self._request_for(method_name, *args)
|
86
|
+
if mapped = self._mapped_method(method_name)
|
87
|
+
params = (args.first.is_a?(Hash) ? args.first : nil)
|
88
|
+
request = Request.new(mapped, self, params)
|
89
|
+
request
|
90
|
+
else
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def [](key)
|
96
|
+
@attributes[key.to_sym]
|
97
|
+
end
|
98
|
+
|
99
|
+
def []=(key, value)
|
100
|
+
_set_attribute(key, value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def each
|
104
|
+
@attributes.each do |key, value|
|
105
|
+
yield key, value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
inspection = if @attributes.any?
|
111
|
+
@attributes.collect { |key, value|
|
112
|
+
"#{key}: #{value_for_inspect(value)}"
|
113
|
+
}.compact.join(", ")
|
114
|
+
else
|
115
|
+
"[uninitialized]"
|
116
|
+
end
|
117
|
+
inspection += "#{"," if @attributes.any?} ETag: #{@_etag}" unless @_etag.nil?
|
118
|
+
inspection += "#{"," if @attributes.any?} Status: #{@_status}" unless @_status.nil?
|
119
|
+
inspection += " (unsaved: #{@dirty_attributes.keys.map(&:to_s).join(", ")})" if @dirty_attributes.any?
|
120
|
+
"#<#{self.class} #{inspection}>"
|
121
|
+
end
|
122
|
+
|
123
|
+
def method_missing(name, *args)
|
124
|
+
if name.to_s[-1,1] == "="
|
125
|
+
name = name.to_s.chop.to_sym
|
126
|
+
_set_attribute(name, args.first)
|
127
|
+
else
|
128
|
+
name_sym = name.to_sym
|
129
|
+
name = name.to_s
|
130
|
+
|
131
|
+
if @attributes.has_key? name_sym
|
132
|
+
@attributes[name_sym]
|
133
|
+
else
|
134
|
+
if name[/^lazy_/] && mapped = self.class._mapped_method(name_sym)
|
135
|
+
if mapped[:method] != :delete
|
136
|
+
raise ValidationFailedException.new if respond_to?(:valid?) && !valid?
|
137
|
+
end
|
138
|
+
|
139
|
+
request = Request.new(mapped, self, args.first)
|
140
|
+
Flexirest::LazyLoader.new(request)
|
141
|
+
elsif mapped = self.class._mapped_method(name_sym)
|
142
|
+
if mapped[:method] != :delete
|
143
|
+
raise ValidationFailedException.new if respond_to?(:valid?) && !valid?
|
144
|
+
end
|
145
|
+
|
146
|
+
request = Request.new(mapped, self, args.first)
|
147
|
+
request.call
|
148
|
+
elsif name[/_was$/] and @attributes.has_key? (name.sub(/_was$/,'').to_sym)
|
149
|
+
k = (name.sub(/_was$/,'').to_sym)
|
150
|
+
@dirty_attributes[k][0]
|
151
|
+
elsif name[/^reset_.*!$/] and @attributes.has_key? (name.sub(/^reset_/,'').sub(/!$/,'').to_sym)
|
152
|
+
k = (name.sub(/^reset_/,'').sub(/!$/,'').to_sym)
|
153
|
+
_reset_attribute(k)
|
154
|
+
elsif self.class.whiny_missing
|
155
|
+
raise NoAttributeException.new("Missing attribute #{name_sym}")
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def respond_to_missing?(method_name, include_private = false)
|
164
|
+
@attributes.has_key? method_name.to_sym
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_hash
|
168
|
+
output = {}
|
169
|
+
@attributes.each do |key, value|
|
170
|
+
if value.is_a? Flexirest::Base
|
171
|
+
output[key.to_s] = value.to_hash
|
172
|
+
elsif value.is_a? Array
|
173
|
+
output[key.to_s] = value.map(&:to_hash)
|
174
|
+
else
|
175
|
+
output[key.to_s] = value
|
176
|
+
end
|
177
|
+
end
|
178
|
+
output
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_json
|
182
|
+
output = to_hash
|
183
|
+
output.to_json
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def _set_attribute(key, value)
|
189
|
+
old_value = @dirty_attributes[key.to_sym]
|
190
|
+
old_value = @attributes[key.to_sym] unless old_value
|
191
|
+
old_value = old_value[0] if old_value and old_value.is_a? Array
|
192
|
+
@dirty_attributes[key.to_sym] = [old_value, value]
|
193
|
+
@attributes[key.to_sym] = value
|
194
|
+
end
|
195
|
+
|
196
|
+
def _reset_attribute(key)
|
197
|
+
old_value = @dirty_attributes[key.to_sym]
|
198
|
+
@attributes[key.to_sym] = old_value[0] if old_value and old_value.is_a? Array
|
199
|
+
@dirty_attributes.delete(key.to_sym)
|
200
|
+
end
|
201
|
+
|
202
|
+
def value_for_inspect(value)
|
203
|
+
if value.is_a?(String) && value.length > 50
|
204
|
+
"#{value[0..50]}...".inspect
|
205
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
206
|
+
%("#{value.to_s(:db)}")
|
207
|
+
else
|
208
|
+
value.inspect
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def parse_date?(name)
|
213
|
+
return true if self.class._date_fields.include?(name)
|
214
|
+
return true if !Flexirest::Base.disable_automatic_date_parsing && self.class._date_fields.empty?
|
215
|
+
false
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
data/lib/flexirest/version.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
describe ActiveModel::Validations do
|
6
|
+
class ActiveModelValidationsExample < Flexirest::BaseWithoutValidation
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
validates :first_name, :last_name, presence: true
|
10
|
+
validates :password, length: { within: 6..12, message: 'Invalid password length, must be 6-12 characters' }
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:first_name) { 'Foo '}
|
14
|
+
let(:last_name) { 'Bar' }
|
15
|
+
let(:password) { 'eiChahya6i' }
|
16
|
+
let(:attributes) { { first_name: first_name, last_name: last_name, password: password } }
|
17
|
+
subject(:instance) { ActiveModelValidationsExample.new(attributes) }
|
18
|
+
|
19
|
+
it { is_expected.to be_valid }
|
20
|
+
|
21
|
+
context 'when the first name is invalid' do
|
22
|
+
let(:first_name) { '' }
|
23
|
+
|
24
|
+
it { is_expected.to_not be_valid }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when the last name is invalid' do
|
28
|
+
let(:last_name) { '' }
|
29
|
+
|
30
|
+
it { is_expected.to_not be_valid }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when the password is invalid' do
|
34
|
+
let(:password) { 'foo' }
|
35
|
+
|
36
|
+
it { is_expected.to_not be_valid }
|
37
|
+
|
38
|
+
it 'should include the custom error message' do
|
39
|
+
instance.valid?
|
40
|
+
|
41
|
+
expect(instance.errors[:password]).to include('Invalid password length, must be 6-12 characters')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -16,24 +16,24 @@ class AssociationExampleBase < Flexirest::Base
|
|
16
16
|
has_one :association_example_other
|
17
17
|
end
|
18
18
|
|
19
|
-
class DeepNestedHasManyChildExample < Flexirest::
|
19
|
+
class DeepNestedHasManyChildExample < Flexirest::BaseWithoutValidation
|
20
20
|
end
|
21
21
|
|
22
|
-
class DeepNestedHasManyTopExample < Flexirest::
|
22
|
+
class DeepNestedHasManyTopExample < Flexirest::BaseWithoutValidation
|
23
23
|
has_many :entries, DeepNestedHasManyChildExample
|
24
24
|
end
|
25
25
|
|
26
|
-
class DeepNestedHasManyExample < Flexirest::
|
26
|
+
class DeepNestedHasManyExample < Flexirest::BaseWithoutValidation
|
27
27
|
has_many :results, DeepNestedHasManyTopExample
|
28
28
|
hash = { results: [ { entries: [ { items: [ "item one", "item two" ] } ] }, { entries: [ { items: [ "item three", "item four" ] } ] } ] }
|
29
29
|
get :find, "/iterate", fake: hash.to_json
|
30
30
|
end
|
31
31
|
|
32
|
-
class WhitelistedDateExample < Flexirest::
|
32
|
+
class WhitelistedDateExample < Flexirest::BaseWithoutValidation
|
33
33
|
parse_date :updated_at
|
34
34
|
end
|
35
35
|
|
36
|
-
class WhitelistedDateMultipleExample < Flexirest::
|
36
|
+
class WhitelistedDateMultipleExample < Flexirest::BaseWithoutValidation
|
37
37
|
parse_date :updated_at, :created_at
|
38
38
|
parse_date :generated_at
|
39
39
|
end
|
data/spec/lib/base_spec.rb
CHANGED
@@ -1,512 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
class
|
4
|
-
whiny_missing true
|
3
|
+
class EmptyBaseExample < Flexirest::Base
|
5
4
|
end
|
6
5
|
|
7
|
-
class TranslatorExample
|
8
|
-
def self.all(object)
|
9
|
-
ret = {}
|
10
|
-
ret["first_name"] = object["name"]
|
11
|
-
ret
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class AlteringClientExample < Flexirest::Base
|
16
|
-
translator TranslatorExample
|
17
|
-
base_url "http://www.example.com"
|
18
|
-
|
19
|
-
get :all, "/all", fake:"{\"name\":\"Billy\"}"
|
20
|
-
get :list, "/list", fake:"{\"name\":\"Billy\", \"country\":\"United Kingdom\"}"
|
21
|
-
get :iterate, "/iterate", fake:"{\"name\":\"Billy\", \"country\":\"United Kingdom\"}"
|
22
|
-
get :find, "/find/:id"
|
23
|
-
end
|
24
|
-
|
25
|
-
class RecordResponseExample < Flexirest::Base
|
26
|
-
base_url "http://www.example.com"
|
27
|
-
|
28
|
-
record_response do |url, response|
|
29
|
-
raise Exception.new("#{url}|#{response.body}")
|
30
|
-
end
|
31
|
-
|
32
|
-
get :all, "/all"
|
33
|
-
end
|
34
|
-
|
35
|
-
class NonHostnameBaseUrlExample < Flexirest::Base
|
36
|
-
base_url "http://www.example.com/v1/"
|
37
|
-
get :all, "/all"
|
38
|
-
end
|
39
|
-
|
40
|
-
class InstanceMethodExample < Flexirest::Base
|
41
|
-
base_url "http://www.example.com/v1/"
|
42
|
-
get :all, "/all"
|
43
|
-
end
|
44
|
-
|
45
|
-
class WhitelistedDateExample < Flexirest::Base
|
46
|
-
parse_date :updated_at
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
6
|
describe Flexirest::Base do
|
51
|
-
|
52
|
-
expect{EmptyExample.new}.to_not raise_error
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should not instantiate a new base class" do
|
56
|
-
expect{Flexirest::Base.new}.to raise_error(Exception)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should save attributes passed in constructor" do
|
60
|
-
client = EmptyExample.new(test: "Something")
|
61
|
-
expect(client._attributes[:test]).to be_a(String)
|
62
|
-
end
|
63
|
-
|
64
|
-
it "should allow attribute reading using missing method names" do
|
65
|
-
client = EmptyExample.new(test: "Something")
|
66
|
-
expect(client.test).to eq("Something")
|
67
|
-
end
|
68
|
-
|
69
|
-
it "should allow attribute reading using [] array notation" do
|
70
|
-
client = EmptyExample.new(test: "Something")
|
71
|
-
expect(client["test"]).to eq("Something")
|
72
|
-
end
|
73
|
-
|
74
|
-
it "allows iteration over attributes using each" do
|
75
|
-
client = AlteringClientExample.iterate
|
76
|
-
expect(client).to be_respond_to(:each)
|
77
|
-
keys = []
|
78
|
-
values = []
|
79
|
-
client.each do |key, value|
|
80
|
-
keys << key ; values << value
|
81
|
-
end
|
82
|
-
expect(keys).to eq(%w{name country}.map(&:to_sym))
|
83
|
-
expect(values).to eq(["Billy", "United Kingdom"])
|
84
|
-
end
|
85
|
-
|
86
|
-
it "should automatically parse ISO 8601 format date and time" do
|
87
|
-
t = Time.now
|
88
|
-
client = EmptyExample.new(test: t.iso8601)
|
89
|
-
expect(client["test"]).to be_an_instance_of(DateTime)
|
90
|
-
expect(client["test"].to_s).to eq(t.to_datetime.to_s)
|
91
|
-
end
|
92
|
-
|
93
|
-
it "should automatically parse ISO 8601 format date and time with milliseconds" do
|
94
|
-
t = Time.now
|
95
|
-
client = EmptyExample.new(test: t.iso8601(3))
|
96
|
-
expect(client["test"]).to be_an_instance_of(DateTime)
|
97
|
-
expect(client["test"].to_s).to eq(t.to_datetime.to_s)
|
98
|
-
end
|
99
|
-
|
100
|
-
it "should automatically parse ISO 8601 format dates" do
|
101
|
-
d = Date.today
|
102
|
-
client = EmptyExample.new(test: d.iso8601)
|
103
|
-
expect(client["test"]).to be_an_instance_of(Date)
|
104
|
-
expect(client["test"]).to eq(d)
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should automatically parse date/time strings regardless if the date portion has no delimiters" do
|
108
|
-
client = EmptyExample.new(test: "20151230T09:48:50-05:00")
|
109
|
-
expect(client["test"]).to be_an_instance_of(DateTime)
|
110
|
-
end
|
111
|
-
|
112
|
-
it "should allow strings of 4 digits and not intepret them as dates" do
|
113
|
-
client = EmptyExample.new(test: "2015")
|
114
|
-
expect(client["test"]).to be_an_instance_of(String)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should allow strings of 8 digits and not intepret them as dates" do
|
118
|
-
client = EmptyExample.new(test: "1266129")
|
119
|
-
expect(client["test"]).to be_an_instance_of(String)
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should store attributes set using missing method names and mark them as dirty" do
|
123
|
-
client = EmptyExample.new()
|
124
|
-
client.test = "Something"
|
125
|
-
expect(client.test.to_s).to eq("Something")
|
126
|
-
expect(client).to be_dirty
|
127
|
-
end
|
128
|
-
|
129
|
-
it "should store attribute set using []= array notation and mark them as dirty" do
|
130
|
-
client = EmptyExample.new()
|
131
|
-
client["test"] = "Something"
|
132
|
-
expect(client["test"].to_s).to eq("Something")
|
133
|
-
expect(client).to be_dirty
|
134
|
-
end
|
135
|
-
|
136
|
-
it "should track changed attributes and provide access to previous values (similar to ActiveRecord/Mongoid)" do
|
137
|
-
client = EmptyExample.new()
|
138
|
-
client["test"] = "Something"
|
139
|
-
|
140
|
-
client._clean! # force a clean state so we can proceed with tests
|
141
|
-
|
142
|
-
expect(client).to_not be_dirty # clean state should have set in (dirty?)
|
143
|
-
expect(client).to_not be_changed # clean state should have set in (changed?)
|
144
|
-
expect(client["test"].to_s).to eq("Something") # verify attribute value persists
|
145
|
-
|
146
|
-
client["test"] = "SomethingElse" # change the attribute value
|
147
|
-
expect(client["test"].to_s).to eq("SomethingElse") # most current set value should be returned
|
148
|
-
expect(client).to be_dirty # an attribute was changed, so the entire object is dirty
|
149
|
-
expect(client).to be_changed # an attribute was changed, so the entire object is changed
|
150
|
-
expect(client.changed).to be_a(Array) # the list of changed attributes should be an Array
|
151
|
-
expect(client.changed).to eq([:test]) # the list of changed attributes should provide the name of the changed attribute
|
152
|
-
expect(client.changes).to be_a(Hash) # changes are returned as a hash
|
153
|
-
expect(client.changes).to eq({test: ["Something", "SomethingElse"]}) # changes include [saved,unsaved] values, keyed by attribute name
|
154
|
-
expect(client.test_was).to eq("Something") # dynamic *_was notation provides original value
|
155
|
-
|
156
|
-
client["test"] = "SomethingElseAgain" # change the attribute value again
|
157
|
-
expect(client.test_was).to eq("Something") # dynamic *_was notation provides original value (from most recent save/load, not most recent change)
|
158
|
-
expect(client.changes).to eq({test: ["Something", "SomethingElseAgain"]}) # changes include [saved,unsaved] values, keyed by attribute name
|
159
|
-
|
160
|
-
# resets the test attribute back to the original value
|
161
|
-
expect( client.reset_test! ).to eq(["Something", "SomethingElseAgain"]) # reseting an attribute returns the previous pending changeset
|
162
|
-
expect(client).to_not be_dirty # reseting an attribute should makeit not dirty again
|
163
|
-
end
|
164
|
-
|
165
|
-
it "should overwrite attributes already set and mark them as dirty" do
|
166
|
-
client = EmptyExample.new(hello: "World")
|
167
|
-
client._clean!
|
168
|
-
expect(client).to_not be_dirty
|
169
|
-
|
170
|
-
client.hello = "Everybody"
|
171
|
-
expect(client).to be_dirty
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'should respond_to? attributes defined in the response' do
|
175
|
-
client = EmptyExample.new(hello: "World")
|
176
|
-
expect(client.respond_to?(:hello)).to be_truthy
|
177
|
-
expect(client.respond_to?(:world)).to be_falsey
|
178
|
-
end
|
179
|
-
|
180
|
-
it "should save the base URL for the API server" do
|
181
|
-
class BaseExample < Flexirest::Base
|
182
|
-
base_url "https://www.example.com/api/v1"
|
183
|
-
end
|
184
|
-
expect(BaseExample.base_url).to eq("https://www.example.com/api/v1")
|
185
|
-
end
|
186
|
-
|
187
|
-
it "should allow changing the base_url while running" do
|
188
|
-
class OutsideBaseExample < Flexirest::Base ; end
|
189
|
-
|
190
|
-
Flexirest::Base.base_url = "https://www.example.com/api/v1"
|
191
|
-
expect(OutsideBaseExample.base_url).to eq("https://www.example.com/api/v1")
|
192
|
-
|
193
|
-
Flexirest::Base.base_url = "https://www.example.com/api/v2"
|
194
|
-
expect(OutsideBaseExample.base_url).to eq("https://www.example.com/api/v2")
|
195
|
-
end
|
196
|
-
|
197
|
-
it "should include the Mapping module" do
|
198
|
-
expect(EmptyExample).to respond_to(:_calls)
|
199
|
-
expect(EmptyExample).to_not respond_to(:_non_existant)
|
200
|
-
end
|
201
|
-
|
202
|
-
it "should be able to easily clean all attributes" do
|
203
|
-
client = EmptyExample.new(hello:"World", goodbye:"Everyone")
|
204
|
-
expect(client).to be_dirty
|
205
|
-
client._clean!
|
206
|
-
expect(client).to_not be_dirty
|
207
|
-
end
|
208
|
-
|
209
|
-
it "should not overly pollute the instance method namespace to reduce chances of clashing (<13 instance methods)" do
|
210
|
-
instance_methods = EmptyExample.instance_methods - Object.methods
|
211
|
-
instance_methods = instance_methods - instance_methods.grep(/^_/)
|
212
|
-
expect(instance_methods.size).to be < 13
|
213
|
-
end
|
214
|
-
|
215
|
-
it "should raise an exception for missing attributes if whiny_missing is enabled" do
|
216
|
-
expect{EmptyExample.new.first_name}.to raise_error(Flexirest::NoAttributeException)
|
217
|
-
end
|
218
|
-
|
219
|
-
it "should be able to lazy instantiate an object from a prefixed lazy_ method call" do
|
220
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
221
|
-
example = AlteringClientExample.lazy_find(1)
|
222
|
-
expect(example).to be_an_instance_of(Flexirest::LazyLoader)
|
223
|
-
expect(example.first_name).to eq("Billy")
|
224
|
-
end
|
225
|
-
|
226
|
-
it "should be able to lazy instantiate an object from a prefixed lazy_ method call from an instance" do
|
227
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
228
|
-
example = AlteringClientExample.new.lazy_find(1)
|
229
|
-
expect(example).to be_an_instance_of(Flexirest::LazyLoader)
|
230
|
-
expect(example.first_name).to eq("Billy")
|
231
|
-
end
|
232
|
-
|
233
|
-
context "#inspect output" do
|
234
|
-
it "displays a nice version" do
|
235
|
-
object = EmptyExample.new(id: 1, name: "John Smith")
|
236
|
-
expect(object.inspect).to match(/#<EmptyExample id: 1, name: "John Smith"/)
|
237
|
-
end
|
238
|
-
|
239
|
-
it "shows dirty attributes as a list of names at the end" do
|
240
|
-
object = EmptyExample.new(id: 1, name: "John Smith")
|
241
|
-
expect(object.inspect).to match(/#<EmptyExample id: 1, name: "John Smith" \(unsaved: id, name\)/)
|
242
|
-
end
|
243
|
-
|
244
|
-
it "doesn't show an empty list of dirty attributes" do
|
245
|
-
object = EmptyExample.new(id: 1, name: "John Smith")
|
246
|
-
object.instance_variable_set(:@dirty_attributes, Set.new)
|
247
|
-
expect(object.inspect).to_not match(/\(unsaved: id, name\)/)
|
248
|
-
end
|
249
|
-
|
250
|
-
it "shows dates in a nice format" do
|
251
|
-
object = EmptyExample.new(dob: Time.new(2015, 01, 02, 03, 04, 05))
|
252
|
-
expect(object.inspect).to match(/#<EmptyExample dob: "2015\-01\-02 03:04:05"/)
|
253
|
-
end
|
254
|
-
|
255
|
-
it "shows the etag if one is set" do
|
256
|
-
object = EmptyExample.new(id: 1)
|
257
|
-
object.instance_variable_set(:@_etag, "sample_etag")
|
258
|
-
expect(object.inspect).to match(/#<EmptyExample id: 1, ETag: sample_etag/)
|
259
|
-
end
|
260
|
-
|
261
|
-
it "shows the HTTP status code if one is set" do
|
262
|
-
object = EmptyExample.new(id: 1)
|
263
|
-
object.instance_variable_set(:@_status, 200)
|
264
|
-
expect(object.inspect).to match(/#<EmptyExample id: 1, Status: 200/)
|
265
|
-
end
|
266
|
-
|
267
|
-
it "shows [uninitialized] for new objects" do
|
268
|
-
object = EmptyExample.new
|
269
|
-
expect(object.inspect).to match(/#<EmptyExample \[uninitialized\]/)
|
270
|
-
end
|
271
|
-
|
272
|
-
end
|
273
|
-
|
274
|
-
context "accepts a Translator to reformat JSON" do
|
275
|
-
it "should log a deprecation warning when using a translator" do
|
276
|
-
expect(Flexirest::Logger).to receive(:warn) do |message|
|
277
|
-
expect(message).to start_with("DEPRECATION")
|
278
|
-
end
|
279
|
-
Proc.new do
|
280
|
-
class DummyExample < Flexirest::Base
|
281
|
-
translator TranslatorExample
|
282
|
-
end
|
283
|
-
end.call
|
284
|
-
end
|
285
|
-
|
286
|
-
it "should call Translator#method when calling the mapped method if it responds to it" do
|
287
|
-
expect(TranslatorExample).to receive(:all).with(an_instance_of(Hash)).and_return({})
|
288
|
-
AlteringClientExample.all
|
289
|
-
end
|
290
|
-
|
291
|
-
it "should not raise errors when calling Translator#method if it does not respond to it" do
|
292
|
-
expect {AlteringClientExample.list}.to_not raise_error
|
293
|
-
end
|
294
|
-
|
295
|
-
it "should translate JSON returned through the Translator" do
|
296
|
-
ret = AlteringClientExample.all
|
297
|
-
expect(ret.first_name).to eq("Billy")
|
298
|
-
expect(ret.name).to be_nil
|
299
|
-
end
|
300
|
-
|
301
|
-
it "should return original JSON for items that aren't handled by the Translator" do
|
302
|
-
ret = AlteringClientExample.list
|
303
|
-
expect(ret.name).to eq("Billy")
|
304
|
-
expect(ret.first_name).to be_nil
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
context "directly call a URL, rather than via a mapped method" do
|
309
|
-
it "should be able to directly call a URL" do
|
310
|
-
expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
311
|
-
EmptyExample._request("http://api.example.com/")
|
312
|
-
end
|
313
|
-
|
314
|
-
it "allows already encoded bodies" do
|
315
|
-
Flexirest::ConnectionManager.reset!
|
316
|
-
connection = double("Connection")
|
317
|
-
allow(connection).to receive(:base_url).and_return("http://api.example.com")
|
318
|
-
expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://api.example.com/").and_return(connection)
|
319
|
-
expect(connection).
|
320
|
-
to receive(:post).
|
321
|
-
with("http://api.example.com/", "{\"test\":\"value\"}",an_instance_of(Hash)).
|
322
|
-
and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"first_name\":\"John\", \"id\":1234}", response_headers:{}, status:200)))
|
323
|
-
EmptyExample._request("http://api.example.com/", :post, {test: "value"}.to_json, request_body_type: :json)
|
324
|
-
end
|
325
|
-
|
326
|
-
it "passes headers" do
|
327
|
-
stub_request(:get, "http://api.example.com/v1").
|
328
|
-
with(headers: {'Accept'=>'application/hal+json, application/json;q=0.5', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Connection'=>'Keep-Alive', 'Content-Type'=>'application/x-www-form-urlencoded', 'X-Something'=>'foo/bar', 'User-Agent'=>/Flexirest\//}).
|
329
|
-
to_return(status: 200, body: "", headers: {})
|
330
|
-
EmptyExample._request("http://api.example.com/v1", :get, {}, {headers: {"X-Something" => "foo/bar"}})
|
331
|
-
end
|
332
|
-
|
333
|
-
it "passes headers if the response is unparsed" do
|
334
|
-
stub_request(:get, "http://api.example.com/v1").
|
335
|
-
with(headers: {'Accept'=>'application/hal+json, application/json;q=0.5', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Connection'=>'Keep-Alive', 'Content-Type'=>'application/x-www-form-urlencoded', 'X-Something'=>'foo/bar', 'User-Agent'=>/Flexirest\//}).
|
336
|
-
to_return(status: 200, body: "", headers: {})
|
337
|
-
EmptyExample._plain_request("http://api.example.com/v1", :get, {}, {headers: {"X-Something" => "foo/bar"}})
|
338
|
-
end
|
339
|
-
|
340
|
-
it "runs callbacks as usual" do
|
341
|
-
expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
342
|
-
expect(EmptyExample).to receive(:_callback_request).with(any_args).exactly(2).times
|
343
|
-
EmptyExample._request("http://api.example.com/")
|
344
|
-
end
|
345
|
-
|
346
|
-
it "should make an HTTP request" do
|
347
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
348
|
-
EmptyExample._request("http://api.example.com/")
|
349
|
-
end
|
350
|
-
|
351
|
-
it "should make an HTTP request including the path in the base_url" do
|
352
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/v1/all', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
353
|
-
NonHostnameBaseUrlExample.all
|
354
|
-
end
|
355
|
-
|
356
|
-
it "should map the response from the directly called URL in the normal way" do
|
357
|
-
expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
358
|
-
example = EmptyExample._request("http://api.example.com/")
|
359
|
-
expect(example.first_name).to eq("Billy")
|
360
|
-
end
|
361
|
-
|
362
|
-
it "should be able to pass the plain response from the directly called URL bypassing JSON loading" do
|
363
|
-
response_body = "This is another non-JSON string"
|
364
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:response_body)))
|
365
|
-
expect(EmptyExample._plain_request("http://api.example.com/", :post, {id:1234})).to eq(response_body)
|
366
|
-
end
|
367
|
-
|
368
|
-
it "should return a PlainResponse from the directly called URL bypassing JSON loading" do
|
369
|
-
response_body = "This is another non-JSON string"
|
370
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:response_body)))
|
371
|
-
expect(EmptyExample._plain_request("http://api.example.com/", :post, {id:1234})).to be_a(Flexirest::PlainResponse)
|
372
|
-
end
|
373
|
-
|
374
|
-
context "Simulating Faraday connection in_parallel" do
|
375
|
-
it "should be able to pass the plain response from the directly called URL bypassing JSON loading" do
|
376
|
-
response_body = "This is another non-JSON string"
|
377
|
-
response = ::FaradayResponseMock.new(
|
378
|
-
OpenStruct.new(status:200, response_headers:{}, body:response_body),
|
379
|
-
false)
|
380
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(response)
|
381
|
-
result = EmptyExample._plain_request("http://api.example.com/", :post, {id:1234})
|
382
|
-
|
383
|
-
expect(result).to eq(nil)
|
384
|
-
|
385
|
-
response.finish
|
386
|
-
expect(result).to eq(response_body)
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
it "should cache plain requests separately" do
|
391
|
-
perform_caching = EmptyExample.perform_caching
|
392
|
-
cache_store = EmptyExample.cache_store
|
393
|
-
begin
|
394
|
-
response = "This is a non-JSON string"
|
395
|
-
other_response = "This is another non-JSON string"
|
396
|
-
allow_any_instance_of(Flexirest::Connection).to receive(:get) do |instance, url, others|
|
397
|
-
if url["/?test=1"]
|
398
|
-
::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:response))
|
399
|
-
else
|
400
|
-
::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:other_response))
|
401
|
-
end
|
402
|
-
end
|
403
|
-
EmptyExample.perform_caching = true
|
404
|
-
EmptyExample.cache_store = TestCacheStore.new
|
405
|
-
expect(EmptyExample._plain_request("http://api.example.com/?test=1")).to eq(response)
|
406
|
-
expect(EmptyExample._plain_request("http://api.example.com/?test=2")).to eq(other_response)
|
407
|
-
ensure
|
408
|
-
EmptyExample.perform_caching = perform_caching
|
409
|
-
EmptyExample.cache_store = cache_store
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
it "should work with caching if instance methods are used" do
|
414
|
-
perform_caching = InstanceMethodExample.perform_caching
|
415
|
-
cache_store = InstanceMethodExample.cache_store
|
416
|
-
begin
|
417
|
-
response = "{\"id\": 1, \"name\":\"test\"}"
|
418
|
-
allow_any_instance_of(Flexirest::Connection).to receive(:get).and_return( ::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{"Etag" => "12345678", "Content-type" => "application/json"}, body:response)))
|
419
|
-
e = InstanceMethodExample.new
|
420
|
-
e.all(1)
|
421
|
-
expect(e.id).to eq(1)
|
422
|
-
ensure
|
423
|
-
InstanceMethodExample.perform_caching = perform_caching
|
424
|
-
InstanceMethodExample.cache_store = cache_store
|
425
|
-
end
|
426
|
-
end
|
427
|
-
|
428
|
-
it "should be able to lazy load a direct URL request" do
|
429
|
-
expect_any_instance_of(Flexirest::Request).to receive(:do_request).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
430
|
-
example = EmptyExample._lazy_request("http://api.example.com/")
|
431
|
-
expect(example).to be_an_instance_of(Flexirest::LazyLoader)
|
432
|
-
expect(example.first_name).to eq("Billy")
|
433
|
-
end
|
434
|
-
|
435
|
-
it "should be able to specify a method and parameters for the call" do
|
436
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:post).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
437
|
-
EmptyExample._request("http://api.example.com/", :post, {id:1234})
|
438
|
-
end
|
439
|
-
|
440
|
-
it "should be able to replace parameters in the URL for the call" do
|
441
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:post).with("http://api.example.com/1234", "", any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
442
|
-
EmptyExample._request("http://api.example.com/:id", :post, {id:1234})
|
443
|
-
end
|
444
|
-
|
445
|
-
it "should be able to use mapped methods to create a request to pass in to _lazy_request" do
|
446
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with('/find/1', anything).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"{\"first_name\":\"Billy\"}")))
|
447
|
-
request = AlteringClientExample._request_for(:find, id: 1)
|
448
|
-
example = AlteringClientExample._lazy_request(request)
|
449
|
-
expect(example.first_name).to eq("Billy")
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
context "Recording a response" do
|
454
|
-
it "calls back to the record_response callback with the url and response body" do
|
455
|
-
expect_any_instance_of(Flexirest::Connection).to receive(:get).with(any_args).and_return(::FaradayResponseMock.new(OpenStruct.new(status:200, response_headers:{}, body:"Hello world")))
|
456
|
-
expect{RecordResponseExample.all}.to raise_error(Exception, "/all|Hello world")
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
context "JSON output" do
|
461
|
-
let(:student1) { EmptyExample.new(name:"John Smith", age:31) }
|
462
|
-
let(:student2) { EmptyExample.new(name:"Bob Brown", age:29) }
|
463
|
-
let(:location) { EmptyExample.new(place:"Room 1408") }
|
464
|
-
let(:lazy) { Laz }
|
465
|
-
let(:object) { EmptyExample.new(name:"Programming 101", location:location, students:[student1, student2]) }
|
466
|
-
let(:json_parsed_object) { MultiJson.load(object.to_json) }
|
467
|
-
|
468
|
-
it "should be able to export to valid json" do
|
469
|
-
expect(object.to_json).to_not be_blank
|
470
|
-
expect{MultiJson.load(object.to_json)}.to_not raise_error
|
471
|
-
end
|
472
|
-
|
473
|
-
it "should not be using Object's #to_json method" do
|
474
|
-
expect(json_parsed_object["dirty_attributes"]).to be_nil
|
475
|
-
end
|
476
|
-
|
477
|
-
it "should recursively convert nested objects" do
|
478
|
-
expect(json_parsed_object["location"]["place"]).to eq(location.place)
|
479
|
-
end
|
480
|
-
|
481
|
-
it "should include arrayed objects" do
|
482
|
-
expect(json_parsed_object["students"]).to be_an_instance_of(Array)
|
483
|
-
expect(json_parsed_object["students"].size).to eq(2)
|
484
|
-
expect(json_parsed_object["students"].first["name"]).to eq(student1.name)
|
485
|
-
expect(json_parsed_object["students"].second["name"]).to eq(student2.name)
|
486
|
-
end
|
487
|
-
|
488
|
-
it "should set integers as a native JSON type" do
|
489
|
-
expect(json_parsed_object["students"].first["age"]).to eq(student1.age)
|
490
|
-
expect(json_parsed_object["students"].second["age"]).to eq(student2.age)
|
491
|
-
end
|
492
|
-
end
|
493
|
-
|
494
|
-
describe "instantiating object" do
|
495
|
-
context "no whitelist specified" do
|
496
|
-
it "should convert dates automatically" do
|
497
|
-
client = EmptyExample.new(test: Time.now.iso8601)
|
498
|
-
expect(client["test"]).to be_an_instance_of(DateTime)
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
context "whitelist specified" do
|
503
|
-
it "should only convert specified dates" do
|
504
|
-
time = Time.now.iso8601
|
505
|
-
client = WhitelistedDateExample.new(updated_at: time, created_at: time)
|
506
|
-
expect(client["updated_at"]).to be_an_instance_of(DateTime)
|
507
|
-
expect(client["created_at"]).to be_an_instance_of(String)
|
508
|
-
end
|
509
|
-
end
|
510
|
-
end
|
7
|
+
subject { EmptyBaseExample.new }
|
511
8
|
|
9
|
+
it { is_expected.to respond_to(:valid?) }
|
512
10
|
end
|