motion_model_resource 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/app/app_delegate.rb +25 -0
- data/{motion/resource/data_parser.rb → lib/motion-model-resource/date_parser.rb} +7 -3
- data/{motion → lib/motion-model-resource}/version.rb +1 -1
- data/lib/motion-model-resource/wrapper.rb +312 -0
- data/lib/motion_model_resource.rb +5 -1
- data/motion_model_resource.gemspec +4 -4
- data/spec/date_parser_spec.rb +17 -0
- data/spec/wrapper_spec.rb +256 -15
- metadata +10 -8
- data/motion/resource/wrapper.rb +0 -237
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MmNkMDQxZTUwYTNmNzBhZmU2ZTE3OGNkNzFhMzlmZTUwNTY5ZjBmNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MzQ3MmVmMjU0OTBmOGNkYTA2YmQ3MmE2YjMwODY4YzFiMjBmNDFhYg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NzAxYTRmOWZmODlmZTgyOTY5MzE2MmNhM2VhMDllMjU0OTU5N2U2MWUyYzY0
|
10
|
+
NTUzYTgxNzU0YWQzMjQwYmNjOWI2MDU5OTk5MzI2MWNhMDVkNDZjZGNhMTI5
|
11
|
+
YTkyMjcxZWY5ODMyNTYxY2UzZmFhYTk0OGEwMTQ4ZjhmMDY2NWI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZDYxOWNlOTNmNGQzMzM3MTRjZWE4NjBjZDVjZjJkZjEyOWRlMjNmMWJiYzU5
|
14
|
+
ODBhZTlmMGJmY2E5NjdkZTBhZjNjNjg4MzUwNTkzMmVhZDhiYWVhNTJjZDk4
|
15
|
+
ZDZjZmJhZmI1MWEyYTcxYmY3ODMxMTRmYWExYzE3MTU1MDkyNTc=
|
data/app/app_delegate.rb
CHANGED
@@ -1,2 +1,27 @@
|
|
1
1
|
class FakeDelegate
|
2
2
|
end
|
3
|
+
|
4
|
+
class Task
|
5
|
+
include MotionModel::Model
|
6
|
+
include MotionModel::ArrayModelAdapter
|
7
|
+
include MotionModelResource::ApiWrapper
|
8
|
+
|
9
|
+
def self.url
|
10
|
+
"http://example.com/tasks"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.wrapper
|
14
|
+
@wrapper ||= {
|
15
|
+
fields: {
|
16
|
+
id: :id,
|
17
|
+
user_id: :user_id,
|
18
|
+
name: :name,
|
19
|
+
},
|
20
|
+
relations: [:user]
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
columns name: :string,
|
25
|
+
updated_at: :date
|
26
|
+
belongs_to :user
|
27
|
+
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
module MotionModelResource
|
2
2
|
module DateParser
|
3
|
-
def self.
|
3
|
+
def self.parse_date(arg)
|
4
|
+
return nil if arg.blank?
|
4
5
|
additional_parsestring = arg.include?(".") ? ".SSS" : ""
|
5
|
-
date_formatter = NSDateFormatter.alloc.init
|
6
6
|
date_formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss#{additional_parsestring}ZZZZ"
|
7
7
|
date = date_formatter.dateFromString arg
|
8
8
|
|
9
9
|
return nil if date.blank?
|
10
10
|
|
11
|
-
date.description
|
11
|
+
"#{date.description}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.date_formatter
|
15
|
+
@date_formatter ||= NSDateFormatter.new
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
module MotionModelResource
|
2
|
+
class WrapperNotDefinedError < Exception; end
|
3
|
+
class URLNotDefinedError < Exception; end
|
4
|
+
class ActionNotImplemented < Exception; end
|
5
|
+
|
6
|
+
module ApiWrapper
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(PublicClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module PublicClassMethods
|
12
|
+
# Returns the last updated at or nil value of Model
|
13
|
+
def last_update
|
14
|
+
return unless columns.include? :updated_at
|
15
|
+
order{|one, two| two.updated_at <=> one.updated_at}.first.try(:updated_at)
|
16
|
+
end
|
17
|
+
|
18
|
+
def lastUpdate
|
19
|
+
NSLog "[DEPRECATED - lastUpdate] please use last_update instead."
|
20
|
+
last_update
|
21
|
+
end
|
22
|
+
|
23
|
+
# Loads the given URL and parse the JSON for new models.
|
24
|
+
# If the models are present, the model will update.
|
25
|
+
# If block given, the block will called, when the the models are saved. The model/s will be passed as an argument to the block.
|
26
|
+
def fetch(site = nil, params = {}, &block)
|
27
|
+
raise MotionModelResource::WrapperNotDefinedError.new "Wrapper is not defined!" unless self.respond_to?(:wrapper)
|
28
|
+
raise MotionModelResource::URLNotDefinedError.new "Resource URL ist not defined! (#{name}.url)" if site.blank? && self.try(:url).blank?
|
29
|
+
|
30
|
+
site = self.url if site.blank?
|
31
|
+
|
32
|
+
BW::HTTP.get(site, params) do |response|
|
33
|
+
models = []
|
34
|
+
if response.ok? && response.body.present?
|
35
|
+
begin
|
36
|
+
json = BW::JSON.parse(response.body.to_str)
|
37
|
+
models = update_models(json)
|
38
|
+
rescue BW::JSON::ParserError
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
block.call(models) if block.present? && block.respond_to?(:call)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parses given JSON object and saves the founded models.
|
47
|
+
# Returns an array with models, or the founded model
|
48
|
+
def update_models(json)
|
49
|
+
if json.is_a?(Array)
|
50
|
+
model_ids = []
|
51
|
+
for json_part in json
|
52
|
+
model = save_model_with(json_part)
|
53
|
+
model_ids << "#{model.id}".to_i if model.present?
|
54
|
+
end
|
55
|
+
where(:id).in model_ids
|
56
|
+
else
|
57
|
+
model = save_model_with(json)
|
58
|
+
return nil if model.blank?
|
59
|
+
|
60
|
+
find("#{model.id}".to_i)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def updateModels(json)
|
65
|
+
NSLog "[DEPRECATED - updateModels] please use update_models instead."
|
66
|
+
update_models json
|
67
|
+
end
|
68
|
+
|
69
|
+
# Builds a model for given JSON object. Returns a new or presend model.
|
70
|
+
def build_model_with(json)
|
71
|
+
return nil if json.is_a?(Array)
|
72
|
+
|
73
|
+
model = where("id").eq(json["id"]).first || self.new
|
74
|
+
model.wrap(json)
|
75
|
+
end
|
76
|
+
|
77
|
+
def buildModel(json)
|
78
|
+
NSLog "[DEPRECATED - buildModel] please use build_model_with instead."
|
79
|
+
build_model_with json
|
80
|
+
end
|
81
|
+
|
82
|
+
# Builds and update/create a model for given JSON object. Returns a new or presend model.
|
83
|
+
def save_model_with(json)
|
84
|
+
return nil if json.is_a?(Array)
|
85
|
+
|
86
|
+
model = build_model_with(json)
|
87
|
+
model.try :save
|
88
|
+
model
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Instance methods
|
93
|
+
|
94
|
+
# When called, the lastSyncAt Column will be set with Time.now (if present)
|
95
|
+
def touch_sync
|
96
|
+
self.lastSyncAt = Time.now if self.respond_to?(:lastSyncAt=)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Saves the current model. Calls super when block is not given.
|
100
|
+
# If block is given, the url method will be needed to call the remote server.
|
101
|
+
# The answer of the server will be parsed and stored.
|
102
|
+
# If the record is a new one, a POST request will be fired, otherwise a PUT call comes to the server.
|
103
|
+
def save(options = {}, &block)
|
104
|
+
if block.present?
|
105
|
+
save_remote(options, &block)
|
106
|
+
else
|
107
|
+
super
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def save_remote(options, &block)
|
112
|
+
raise MotionModelResource::URLNotDefinedError.new "URL is not defined for #{self.class.name}!" unless self.class.respond_to?(:url)
|
113
|
+
|
114
|
+
self.id = nil if self.id.present? && save_action == :create
|
115
|
+
|
116
|
+
params = build_hash_from_model(self.class.name.underscore, self)
|
117
|
+
params.merge!(options[:params]) if options[:params].present?
|
118
|
+
|
119
|
+
model = self
|
120
|
+
|
121
|
+
save_remote_call(params) do |response|
|
122
|
+
if response.ok? && response.body.present?
|
123
|
+
begin
|
124
|
+
json = BW::JSON.parse(response.body.to_str)
|
125
|
+
|
126
|
+
model.wrap json
|
127
|
+
model.save
|
128
|
+
model.touch_sync
|
129
|
+
rescue BW::JSON::ParserError
|
130
|
+
model = nil
|
131
|
+
end
|
132
|
+
else
|
133
|
+
model = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
block.call(model, json) if block.present? && block.respond_to?(:call)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def destroy(options = {}, &block)
|
141
|
+
if block.present?
|
142
|
+
destroy_remote(options, &block)
|
143
|
+
else
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Destroys a remote model
|
149
|
+
# UNTESTED # TODO write a test
|
150
|
+
def destroy_remote(options = {}, &block)
|
151
|
+
raise MotionModelResource::URLNotDefinedError.new "URL is not defined for #{self.class.name}!" unless self.class.respond_to?(:url)
|
152
|
+
|
153
|
+
model = self
|
154
|
+
|
155
|
+
BW::HTTP.delete(save_url, {payload: options[:params]}) do |response|
|
156
|
+
if response.ok? || options[:force] == true
|
157
|
+
model.delete
|
158
|
+
end
|
159
|
+
|
160
|
+
block.call if block.present? && block.respond_to?(:call)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Takes no care of the server response.
|
165
|
+
# UNTESTED # TODO write a test
|
166
|
+
def destroy!(options = {}, &block)
|
167
|
+
options.merge!(force: true)
|
168
|
+
|
169
|
+
destroy_remote(options, &block)
|
170
|
+
end
|
171
|
+
alias_method :destroy_remote!, :destroy!
|
172
|
+
|
173
|
+
# Returns a hash with given model
|
174
|
+
def build_hash_from_model(main_key, model)
|
175
|
+
hash = {
|
176
|
+
main_key => {}
|
177
|
+
}
|
178
|
+
hash[main_key] = {}
|
179
|
+
|
180
|
+
model.attributes.each do |key, attribute|
|
181
|
+
if model.class.has_many_columns.keys.include?(key)
|
182
|
+
new_key = attribute.first.class.name.pluralize.underscore
|
183
|
+
hash[main_key][new_key] = []
|
184
|
+
for a in attribute
|
185
|
+
hash[main_key][new_key].push(build_hash_from_model(new_key, a)[new_key])
|
186
|
+
end
|
187
|
+
elsif attribute.respond_to?(:attributes)
|
188
|
+
new_key = attribute.class.name.underscore
|
189
|
+
h = attribute.build_hash_from_model(new_key, attribute)
|
190
|
+
hash[main_key][new_key] = h[new_key] if h.has_key?(new_key)
|
191
|
+
else
|
192
|
+
model.class.wrapper[:fields].each do |wrapper_key, wrapper_value|
|
193
|
+
hash[main_key][wrapper_key] = attribute if wrapper_value == key
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
hash
|
199
|
+
end
|
200
|
+
|
201
|
+
def buildHashFromModel(mainKey, model)
|
202
|
+
NSLog "[DEPRECATED - buildHashFromModel] please use build_hash_from_model instead."
|
203
|
+
build_hash_from_model mainKey, model
|
204
|
+
end
|
205
|
+
|
206
|
+
# Loads the given URL and parse the JSON for a model.
|
207
|
+
# If the model is present, the model will updates.
|
208
|
+
# If block given, the block will called, when the the model is saved. The model will be passed as an argument to the block.
|
209
|
+
def fetch(site = nil, params = {}, &block)
|
210
|
+
raise MotionModelResource::URLNotDefinedError.new "Resource URL ist not defined! (#{self.class.name}.url)" if site.blank? && self.class.try(:url).blank?
|
211
|
+
raise MotionModelResource::WrapperNotDefinedError.new "Wrapper is not defined!" unless self.class.respond_to?(:wrapper)
|
212
|
+
|
213
|
+
site = "#{self.class.url}/#{id}" if site.blank?
|
214
|
+
|
215
|
+
model = self
|
216
|
+
BW::HTTP.get(site, params) do |response|
|
217
|
+
if response.ok? && response.body.present?
|
218
|
+
begin
|
219
|
+
json = BW::JSON.parse(response.body.to_str)
|
220
|
+
model.wrap(json)
|
221
|
+
|
222
|
+
model.save
|
223
|
+
rescue BW::JSON::ParserError
|
224
|
+
model = nil
|
225
|
+
end
|
226
|
+
else
|
227
|
+
model = nil
|
228
|
+
end
|
229
|
+
|
230
|
+
block.call model if block.present? && block.respond_to?(:call)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Wraps the current model with the given JSON.
|
235
|
+
# All the fields found in JSON and self.wrapper will be parsed.
|
236
|
+
# Returns true, when no error exists
|
237
|
+
def wrap(model_json)
|
238
|
+
return unless self.class.respond_to?(:wrapper)
|
239
|
+
|
240
|
+
touch_sync
|
241
|
+
|
242
|
+
self.class.wrapper[:fields].each do |online, local|
|
243
|
+
if model_json.respond_to?("key?") && model_json.key?("#{online}")
|
244
|
+
value = parse_value(local, model_json["#{online}"])
|
245
|
+
self.send("#{local}=", value)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
if self.class.wrapper[:relations].present?
|
250
|
+
self.class.wrapper[:relations].each do |relation|
|
251
|
+
if model_json.respond_to?("key?") && model_json.key?("#{relation}") && model_json["#{relation}"].present?
|
252
|
+
klass_name = column(relation.to_s).instance_variable_get("@options").try(:[], :joined_class_name) || relation.to_s.singularize.camelize
|
253
|
+
|
254
|
+
klass = Object.const_get(klass_name)
|
255
|
+
|
256
|
+
new_relation = klass.update_models(model_json["#{relation}"])
|
257
|
+
self.send("#{relation}=", new_relation) rescue NoMethodError # not correct implemented in MotionModel
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
self
|
263
|
+
end
|
264
|
+
|
265
|
+
# Parses given value for key in the right format for MotionModel.
|
266
|
+
# Currently only Date/Time support needed
|
267
|
+
def parse_value(key, value)
|
268
|
+
case self.column_type(key.to_sym)
|
269
|
+
when :date, :time then MotionModelResource::DateParser.parse_date value
|
270
|
+
else value
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def parseValue(key, value)
|
275
|
+
NSLog "[DEPRECATED - parseValue] please use parse_value instead."
|
276
|
+
parse_value
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
# Returns the action for save
|
282
|
+
def save_action
|
283
|
+
if new_record?
|
284
|
+
:create
|
285
|
+
elsif self.id.present?
|
286
|
+
:update
|
287
|
+
else
|
288
|
+
raise MotionModelResource::ActionNotImplemented.new "Action ist not implemented for #{self.class.name}!"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Returns the URL for the resource
|
293
|
+
def save_url
|
294
|
+
raise MotionModelResource::URLNotDefinedError.new "URL is not defined for #{self.class.name}!" unless self.class.respond_to?(:url)
|
295
|
+
|
296
|
+
case save_action
|
297
|
+
when :create then self.try(:url) || self.class.url
|
298
|
+
when :update then self.try(:url) || "#{self.class.url}/#{id}"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Calls a request to the remote server with the given params.
|
303
|
+
def save_remote_call(params, &request_block)
|
304
|
+
case save_action
|
305
|
+
when :create
|
306
|
+
BW::HTTP.post(save_url, {payload: params}, &request_block)
|
307
|
+
when :update
|
308
|
+
BW::HTTP.put(save_url, {payload: params}, &request_block)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
1
5
|
Motion::Project::App.setup do |app|
|
2
|
-
Dir.glob(File.join(File.
|
6
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'motion-model-resource/*.rb')).each do |file|
|
3
7
|
app.files.unshift(file)
|
4
8
|
end
|
5
9
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path('../motion/version', __FILE__)
|
2
|
+
require File.expand_path('../lib/motion-model-resource/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Torben Toepper"]
|
6
|
-
gem.email = ["
|
7
|
-
gem.description = "Simple JSON API Wrapper for MotionModel on RubyMotion"
|
8
|
-
gem.summary = "Simple JSON API Wrapper for MotionModel on RubyMotion"
|
6
|
+
gem.email = ["message@torbentoepper.de"]
|
7
|
+
gem.description = "Simple REST JSON API Wrapper for MotionModel on RubyMotion"
|
8
|
+
gem.summary = "Simple REST JSON API Wrapper for MotionModel on RubyMotion"
|
9
9
|
gem.homepage = "https://github.com/torben/motion-resource"
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe '#date_parser' do
|
2
|
+
describe '#parseValue' do
|
3
|
+
it 'should parse a date in the right format for MotionModel' do
|
4
|
+
time_string = "2013-11-03T16:59:35+01:00"
|
5
|
+
|
6
|
+
MotionModelResource::DateParser.parse_date(time_string).should.equal("2013-11-03 15:59:35 +0000")
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return nil, when giving a wrong time' do
|
10
|
+
time_string = "2342"
|
11
|
+
time_string2 = "2013-21-03T16:59:35+01:00"
|
12
|
+
|
13
|
+
MotionModelResource::DateParser.parse_date(time_string).should.equal nil
|
14
|
+
MotionModelResource::DateParser.parse_date(time_string2).should.equal nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/wrapper_spec.rb
CHANGED
@@ -31,11 +31,12 @@ class User
|
|
31
31
|
}
|
32
32
|
end
|
33
33
|
|
34
|
-
columns name:
|
35
|
-
plan_id:
|
36
|
-
email:
|
37
|
-
age:
|
38
|
-
admin:
|
34
|
+
columns name: :string,
|
35
|
+
plan_id: :plan_id,
|
36
|
+
email: :string,
|
37
|
+
age: :integer,
|
38
|
+
admin: :boolean,
|
39
|
+
lastSyncAt: :time
|
39
40
|
|
40
41
|
has_many :tasks
|
41
42
|
belongs_to :plan
|
@@ -61,7 +62,9 @@ class Task
|
|
61
62
|
}
|
62
63
|
end
|
63
64
|
|
64
|
-
columns name:
|
65
|
+
columns name: :string,
|
66
|
+
due_date: :time,
|
67
|
+
updated_at: :date
|
65
68
|
belongs_to :user
|
66
69
|
end
|
67
70
|
|
@@ -88,6 +91,24 @@ class Plan
|
|
88
91
|
has_many :users
|
89
92
|
end
|
90
93
|
|
94
|
+
class PlanWithoutUrl
|
95
|
+
include MotionModel::Model
|
96
|
+
include MotionModel::ArrayModelAdapter
|
97
|
+
include MotionModelResource::ApiWrapper
|
98
|
+
|
99
|
+
def self.wrapper
|
100
|
+
@wrapper ||= {
|
101
|
+
fields: {
|
102
|
+
id: :id,
|
103
|
+
name: :name,
|
104
|
+
},
|
105
|
+
relations: [:users]
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
columns name: :string
|
110
|
+
end
|
111
|
+
|
91
112
|
describe "Fetching a model" do
|
92
113
|
extend WebStub::SpecHelpers
|
93
114
|
|
@@ -118,6 +139,7 @@ describe "Fetching a model" do
|
|
118
139
|
user.email.should.equal("peter@pan.de")
|
119
140
|
user.age.should.equal(14)
|
120
141
|
user.admin.should.equal(false)
|
142
|
+
user.lastSyncAt.should.not == nil
|
121
143
|
end
|
122
144
|
end
|
123
145
|
|
@@ -168,19 +190,238 @@ describe "Fetching a model" do
|
|
168
190
|
end
|
169
191
|
end
|
170
192
|
|
171
|
-
describe '
|
172
|
-
|
173
|
-
|
193
|
+
describe 'Class Methods' do
|
194
|
+
describe '#build_model_with' do
|
195
|
+
it 'should build a new Task object' do
|
196
|
+
json = {
|
197
|
+
"id" => 929,
|
198
|
+
"name" => 'Cleaning up the closet',
|
199
|
+
"user_id" => 10
|
200
|
+
}
|
201
|
+
|
202
|
+
t = Task.build_model_with(json)
|
203
|
+
|
204
|
+
t.name.should == "Cleaning up the closet"
|
205
|
+
t.user_id.should == 10
|
206
|
+
t.class == Task
|
207
|
+
t.new_record?.should == true
|
208
|
+
|
209
|
+
t.save
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should return and update an existing model' do
|
213
|
+
json = {
|
214
|
+
"id" => 929,
|
215
|
+
"name" => 'A new name',
|
216
|
+
"user_id" => 10
|
217
|
+
}
|
218
|
+
|
219
|
+
t = Task.build_model_with(json)
|
220
|
+
|
221
|
+
t.name.should == "A new name"
|
222
|
+
t.user_id.should == 10
|
223
|
+
t.class == Task
|
224
|
+
t.new_record?.should == false
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should retun nil, if array is given' do
|
228
|
+
json = [{
|
229
|
+
id: 923,
|
230
|
+
name: 'Cleaning up the closet',
|
231
|
+
user_id: 10
|
232
|
+
},
|
233
|
+
{
|
234
|
+
id: 924,
|
235
|
+
name: 'Cleaning up the closet2',
|
236
|
+
user_id: 11
|
237
|
+
}]
|
238
|
+
|
239
|
+
Task.build_model_with(json).should == nil
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe '#create_model_with' do
|
244
|
+
it 'should create a new Task object' do
|
245
|
+
json = {
|
246
|
+
"id" => 828,
|
247
|
+
"name" => 'An Amazing Name!',
|
248
|
+
"user_id" => 12
|
249
|
+
}
|
250
|
+
|
251
|
+
t = Task.save_model_with(json)
|
252
|
+
|
253
|
+
t.name.should == "An Amazing Name!"
|
254
|
+
t.user_id.should == 12
|
255
|
+
t.class == Task
|
256
|
+
t.new_record?.should == false
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'should return and update an existing model' do
|
260
|
+
json = {
|
261
|
+
"id" => 828,
|
262
|
+
"name" => 'A newer name',
|
263
|
+
"user_id" => 12
|
264
|
+
}
|
265
|
+
|
266
|
+
t = Task.save_model_with(json)
|
267
|
+
t.new_record?.should == false
|
268
|
+
|
269
|
+
t2 = Task.find(828)
|
270
|
+
t2.name.should == "A newer name"
|
271
|
+
t2.user_id.should == 12
|
272
|
+
t2.class == Task
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'should retun nil, if array is given' do
|
276
|
+
json = [{
|
277
|
+
id: 923,
|
278
|
+
name: 'Cleaning up the closet',
|
279
|
+
user_id: 10
|
280
|
+
},
|
281
|
+
{
|
282
|
+
id: 924,
|
283
|
+
name: 'Cleaning up the closet2',
|
284
|
+
user_id: 11
|
285
|
+
}]
|
286
|
+
|
287
|
+
Task.build_model_with(json).should == nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '#update_models' do
|
292
|
+
before do
|
293
|
+
3.times { User.create }
|
294
|
+
Task.create(id: 22)
|
295
|
+
Task.create(id: 33)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should return nil, if model has no updated_at column' do
|
299
|
+
User.last_update.should == nil
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should return last updated_at, if model has updated_at column' do
|
303
|
+
Task.last_update.to_s.should.equal Task.find(33).updated_at.to_s
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
describe '#touch_sync' do
|
308
|
+
it 'should sets a timestap when calling' do
|
309
|
+
u = User.new
|
310
|
+
u.touch_sync
|
311
|
+
u.lastSyncAt.class.should == Time
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'should return nil if model has no lastSyncAt column' do
|
315
|
+
t = Task.new
|
316
|
+
t.touch_sync.should == nil
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
describe 'Instance Methods' do
|
322
|
+
describe '#parse_value' do
|
323
|
+
it 'should parse a date string to a time string' do
|
324
|
+
t = Task.new
|
325
|
+
t.parse_value(:due_date, "2014-05-29T10:59:39+02:00").should == "2014-05-29 08:59:39 +0000"
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'should return nil if the input date is invalid' do
|
329
|
+
t = Task.new
|
330
|
+
t.parse_value(:due_date, "2014-05-29 10:59:39").should == nil
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe '#save' do
|
335
|
+
it 'should return nil, if the remote server returns empty json' do
|
336
|
+
stub_request(:post, Task.url).to_return(json: {})
|
337
|
+
t = Task.new
|
338
|
+
t.name = "This is Sparta!"
|
339
|
+
|
340
|
+
new_model = nil
|
341
|
+
t.save do |model|
|
342
|
+
resume
|
343
|
+
new_model = model
|
344
|
+
end
|
345
|
+
|
346
|
+
wait_max 1.0 do
|
347
|
+
new_model.should == nil
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
it 'should save a model and call the remote server' do
|
352
|
+
stub_request(:post, Task.url).to_return(json: {id: 88, name: 'This is Sparta!'})
|
353
|
+
t = Task.new
|
354
|
+
t.name = "This is Sparta!"
|
174
355
|
|
175
|
-
|
356
|
+
@new_model = nil
|
357
|
+
t.save do |model|
|
358
|
+
@new_model = model
|
359
|
+
resume
|
360
|
+
end
|
361
|
+
|
362
|
+
wait_max 1.0 do
|
363
|
+
@new_model.should.not == nil
|
364
|
+
@new_model.new_record?.should == false
|
365
|
+
@new_model.name.should == "This is Sparta!"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
it 'should update a model and call the remote server' do
|
370
|
+
t = Task.last
|
371
|
+
t.name = "This was Sparta :("
|
372
|
+
stub_request(:put, "#{Task.url}/#{t.id}").to_return(json: {name: 'This was Sparta :('})
|
373
|
+
|
374
|
+
@update_model = nil
|
375
|
+
t.save do |model|
|
376
|
+
@update_model = model
|
377
|
+
resume
|
378
|
+
end
|
379
|
+
|
380
|
+
wait_max 1.0 do
|
381
|
+
@update_model.should.not == nil
|
382
|
+
@update_model.new_record?.should == false
|
383
|
+
@update_model.name.should == "This was Sparta :("
|
384
|
+
end
|
385
|
+
end
|
176
386
|
end
|
177
387
|
|
178
|
-
|
179
|
-
|
180
|
-
|
388
|
+
describe '#save_action' do
|
389
|
+
it 'should return :create, when having a new record' do
|
390
|
+
t = Task.new
|
391
|
+
t.send(:save_action).should == :create
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'should return :update, when having a new record' do
|
395
|
+
t = Task.last
|
396
|
+
t.send(:save_action).should == :update
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'should raise an exception, when having a new record with an id' do
|
400
|
+
t = Task.last
|
401
|
+
t.id = nil
|
402
|
+
lambda {
|
403
|
+
t.send(:save_action)
|
404
|
+
}.should.raise(MotionModelResource::ActionNotImplemented)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
describe '#save_url' do
|
409
|
+
it 'should raise an exception, when url property is missing' do
|
410
|
+
p = PlanWithoutUrl.new
|
411
|
+
lambda {
|
412
|
+
p.send(:save_url)
|
413
|
+
}.should.raise(MotionModelResource::URLNotDefinedError)
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'should return the right create url' do
|
417
|
+
t = Task.new
|
418
|
+
t.send(:save_url).should == Task.url
|
419
|
+
end
|
181
420
|
|
182
|
-
|
183
|
-
|
421
|
+
it 'should return the right update url' do
|
422
|
+
t = Task.first
|
423
|
+
t.send(:save_url).should == "#{Task.url}/#{t.id}"
|
424
|
+
end
|
184
425
|
end
|
185
426
|
end
|
186
427
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion_model_resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Torben Toepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bubble-wrap
|
@@ -66,9 +66,9 @@ dependencies:
|
|
66
66
|
- - ! '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.3.0
|
69
|
-
description: Simple JSON API Wrapper for MotionModel on RubyMotion
|
69
|
+
description: Simple REST JSON API Wrapper for MotionModel on RubyMotion
|
70
70
|
email:
|
71
|
-
-
|
71
|
+
- message@torbentoepper.de
|
72
72
|
executables: []
|
73
73
|
extensions: []
|
74
74
|
extra_rdoc_files: []
|
@@ -81,11 +81,12 @@ files:
|
|
81
81
|
- README.md
|
82
82
|
- Rakefile
|
83
83
|
- app/app_delegate.rb
|
84
|
+
- lib/motion-model-resource/date_parser.rb
|
85
|
+
- lib/motion-model-resource/version.rb
|
86
|
+
- lib/motion-model-resource/wrapper.rb
|
84
87
|
- lib/motion_model_resource.rb
|
85
|
-
- motion/resource/data_parser.rb
|
86
|
-
- motion/resource/wrapper.rb
|
87
|
-
- motion/version.rb
|
88
88
|
- motion_model_resource.gemspec
|
89
|
+
- spec/date_parser_spec.rb
|
89
90
|
- spec/wrapper_spec.rb
|
90
91
|
homepage: https://github.com/torben/motion-resource
|
91
92
|
licenses:
|
@@ -110,6 +111,7 @@ rubyforge_project:
|
|
110
111
|
rubygems_version: 2.1.11
|
111
112
|
signing_key:
|
112
113
|
specification_version: 3
|
113
|
-
summary: Simple JSON API Wrapper for MotionModel on RubyMotion
|
114
|
+
summary: Simple REST JSON API Wrapper for MotionModel on RubyMotion
|
114
115
|
test_files:
|
116
|
+
- spec/date_parser_spec.rb
|
115
117
|
- spec/wrapper_spec.rb
|
data/motion/resource/wrapper.rb
DELETED
@@ -1,237 +0,0 @@
|
|
1
|
-
module MotionModelResource
|
2
|
-
class WrapperNotDefinedError < Exception; end
|
3
|
-
class URLNotDefinedError < Exception; end
|
4
|
-
class ActionNotImplemented < Exception; end
|
5
|
-
|
6
|
-
module ApiWrapper
|
7
|
-
def self.included(base)
|
8
|
-
base.extend(PublicClassMethods)
|
9
|
-
end
|
10
|
-
|
11
|
-
module PublicClassMethods
|
12
|
-
# Returns the last updated at or nil value of Model
|
13
|
-
def lastUpdate
|
14
|
-
order(:updated_at).first.try(:updated_at)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Loads the given URL and parse the JSON for new models.
|
18
|
-
# If the models are present, the model will update.
|
19
|
-
# If block given, the block will called, when the the models are saved. The model/s will be passed as an argument to the block.
|
20
|
-
def fetch(site, params = {}, &block)
|
21
|
-
raise MotionModelResource::WrapperNotDefinedError.new "Wrapper is not defined!" unless self.respond_to?(:wrapper)
|
22
|
-
BW::HTTP.get(site, params) do |response|
|
23
|
-
models = []
|
24
|
-
if response.ok? && response.body.present?
|
25
|
-
json = BW::JSON.parse(response.body.to_str)
|
26
|
-
models = updateModels(json)
|
27
|
-
end
|
28
|
-
|
29
|
-
block.call(models) if block.present? && block.respond_to?(:call)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Parses given JSON object and saves the founded models.
|
34
|
-
# Returns an array with models, or the founded model
|
35
|
-
def updateModels(json)
|
36
|
-
if json.is_a?(Array)
|
37
|
-
models = []
|
38
|
-
for jsonPart in json
|
39
|
-
model = buildModel(jsonPart)
|
40
|
-
if model.present?
|
41
|
-
model.save
|
42
|
-
models << model
|
43
|
-
end
|
44
|
-
end
|
45
|
-
return models
|
46
|
-
else
|
47
|
-
model = buildModel(json)
|
48
|
-
if model.present?
|
49
|
-
model.save
|
50
|
-
return model
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Builds a model for given JSON object. Returns a new or presend model.
|
56
|
-
def buildModel(json)
|
57
|
-
classname = name.underscore
|
58
|
-
|
59
|
-
model = where("id").eq(json["id"]).first
|
60
|
-
if model.present?
|
61
|
-
if model.wrap(json)
|
62
|
-
model.lastSyncAt = Time.now if model.respond_to?(:lastSyncAt)
|
63
|
-
return model
|
64
|
-
end
|
65
|
-
else
|
66
|
-
newModel = self.new
|
67
|
-
return newModel if newModel.wrap(json)
|
68
|
-
end
|
69
|
-
|
70
|
-
return nil
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# Instance methods
|
75
|
-
|
76
|
-
# Saves the current model. Calls super when block is not given.
|
77
|
-
# If block is given, the url method will be needed to call the remote server.
|
78
|
-
# The answer of the server will be parsed and stored.
|
79
|
-
# If the record is a new one, a POST request will be fired, otherwise a PUT call comes to the server.
|
80
|
-
def save(options = {}, &block)
|
81
|
-
if block.present?
|
82
|
-
raise MotionModelResource::URLNotDefinedError.new "URL is not defined for #{self.class.name}!" unless self.class.respond_to?(:url)
|
83
|
-
|
84
|
-
action = if new_record?
|
85
|
-
"create"
|
86
|
-
elsif self.id.present?
|
87
|
-
"update"
|
88
|
-
else
|
89
|
-
raise MotionModelResource::ActionNotImplemented.new "Action ist not implemented for #{self.class.name}!"
|
90
|
-
end
|
91
|
-
|
92
|
-
model = self
|
93
|
-
|
94
|
-
model.id = nil if model.id.present? && action == "create"
|
95
|
-
|
96
|
-
hash = buildHashFromModel(self.class.name.underscore, self)
|
97
|
-
hash.merge!(options[:params]) if options[:params].present?
|
98
|
-
|
99
|
-
requestBlock = Proc.new do |response|
|
100
|
-
if response.ok? && response.body.present?
|
101
|
-
json = BW::JSON.parse(response.body.to_str)
|
102
|
-
|
103
|
-
model.wrap(json)
|
104
|
-
model.lastSyncAt = Time.now if model.respond_to?(:lastSyncAt)
|
105
|
-
model.save
|
106
|
-
else
|
107
|
-
model = nil
|
108
|
-
end
|
109
|
-
|
110
|
-
block.call(model) if block.present? && block.respond_to?(:call)
|
111
|
-
end
|
112
|
-
|
113
|
-
case action
|
114
|
-
when "create"
|
115
|
-
BW::HTTP.post(self.try(:url) || self.class.url, {payload: hash}, &requestBlock)
|
116
|
-
when "update"
|
117
|
-
BW::HTTP.put("#{self.try(:url)}" || "#{self.class.url}/#{model.id}", {payload: hash}, &requestBlock)
|
118
|
-
end
|
119
|
-
else
|
120
|
-
super
|
121
|
-
end
|
122
|
-
end
|
123
|
-
alias_method :save_remote, :save
|
124
|
-
|
125
|
-
# Destroys a remote model
|
126
|
-
# UNTESTED # TODO write a test
|
127
|
-
def destroy(options = {}, &block)
|
128
|
-
if block.present?
|
129
|
-
raise MotionModelResource::URLNotDefinedError.new "URL is not defined for #{self.class.name}!" unless self.class.respond_to?(:url)
|
130
|
-
|
131
|
-
model = self
|
132
|
-
|
133
|
-
BW::HTTP.delete("#{self.try(:url)}" || "#{self.class.url}/#{model.id}", {payload: options[:params]}) do |response|
|
134
|
-
if response.ok? || options[:force] == true
|
135
|
-
model.delete
|
136
|
-
end
|
137
|
-
|
138
|
-
block.call if block.present? && block.respond_to?(:call)
|
139
|
-
end
|
140
|
-
else
|
141
|
-
super
|
142
|
-
end
|
143
|
-
end
|
144
|
-
alias_method :destroy_remote, :destroy
|
145
|
-
|
146
|
-
# Takes no care of the server response.
|
147
|
-
# UNTESTED # TODO write a test
|
148
|
-
def destroy!(options = {}, &block)
|
149
|
-
options.merge!(force: true)
|
150
|
-
|
151
|
-
destroy(options, &block)
|
152
|
-
end
|
153
|
-
alias_method :destroy_remote!, :destroy!
|
154
|
-
|
155
|
-
# Returns a hash with given model
|
156
|
-
def buildHashFromModel(mainKey, model)
|
157
|
-
hash = {
|
158
|
-
mainKey => {}
|
159
|
-
}
|
160
|
-
hash[mainKey] = {}
|
161
|
-
|
162
|
-
model.attributes.each do |key, attribute|
|
163
|
-
if model.class.has_many_columns.keys.include?(key)
|
164
|
-
newKey = attribute.first.class.name.pluralize.downcase
|
165
|
-
hash[mainKey][newKey] = []
|
166
|
-
for a in attribute
|
167
|
-
hash[mainKey][newKey].push(buildHashFromModel(newKey, a)[newKey])
|
168
|
-
end
|
169
|
-
elsif attribute.respond_to?(:attributes)
|
170
|
-
newKey = attribute.class.name.underscore
|
171
|
-
h = buildHashFromModel(newKey, attribute)
|
172
|
-
hash[mainKey][newKey] = h[newKey] if h.has_key?(newKey)
|
173
|
-
else
|
174
|
-
model.class.wrapper[:fields].each do |wrapperKey, wrapperValue|
|
175
|
-
hash[mainKey][wrapperKey] = attribute if wrapperValue == key
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
return hash
|
181
|
-
end
|
182
|
-
|
183
|
-
# Loads the given URL and parse the JSON for a model.
|
184
|
-
# If the model is present, the model will updates.
|
185
|
-
# If block given, the block will called, when the the model is saved. The model will be passed as an argument to the block.
|
186
|
-
def fetch(site, params = {}, &block)
|
187
|
-
raise MotionModelResource::WrapperNotDefinedError.new "Wrapper is not defined!" unless self.class.respond_to?(:wrapper)
|
188
|
-
model = self
|
189
|
-
BW::HTTP.get(site, params) do |response|
|
190
|
-
if response.ok? && response.body.present?
|
191
|
-
json = BW::JSON.parse(response.body.to_str)
|
192
|
-
model.wrap(json)
|
193
|
-
model.lastSyncAt = Time.now if model.respond_to?(:lastSyncAt)
|
194
|
-
|
195
|
-
model.save
|
196
|
-
end
|
197
|
-
|
198
|
-
block.call if block.present? && block.respond_to?(:call)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Wraps the current model with the given JSON.
|
203
|
-
# All the fields found in JSON and self.wrapper will be parsed.
|
204
|
-
# Returns true, when no error exists
|
205
|
-
def wrap(modelJson)
|
206
|
-
return unless self.class.respond_to?(:wrapper)
|
207
|
-
|
208
|
-
self.class.wrapper[:fields].each do |online, local|
|
209
|
-
if modelJson.respond_to?("key?") && modelJson.key?("#{online}")
|
210
|
-
value = parseValue(local, modelJson[online])
|
211
|
-
self.send("#{local}=", value)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
if self.class.wrapper[:relations].present?
|
216
|
-
self.class.wrapper[:relations].each do |relation|
|
217
|
-
if modelJson.respond_to?("key?") && modelJson.key?("#{relation}") && modelJson["#{relation}"].present?
|
218
|
-
klass = Object.const_get(relation.to_s.singularize.camelize)
|
219
|
-
newRelation = klass.updateModels(modelJson["#{relation}"])
|
220
|
-
self.send("#{relation}=", newRelation) rescue NoMethodError # not correct implemented in MotionModel
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
true
|
226
|
-
end
|
227
|
-
|
228
|
-
# Parses given value for key in the right format for MotionModel.
|
229
|
-
# Currently only Date/Time support needed
|
230
|
-
def parseValue(key, value)
|
231
|
-
case self.column_type(key.to_sym)
|
232
|
-
when :time then MotionModelResource::DateParser.parseDate value
|
233
|
-
else value
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|