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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTc1MTZkYTZhM2QyYjFkMjViN2VjNDNlNDhmMmUxNzhlZjdhZjMyZQ==
4
+ MmNkMDQxZTUwYTNmNzBhZmU2ZTE3OGNkNzFhMzlmZTUwNTY5ZjBmNQ==
5
5
  data.tar.gz: !binary |-
6
- ZDEyNzY4ZjVmZTc0ODQ4MzY4Y2EzNmM4YWM0ZDMyZDYyMDc3NGE5Yg==
6
+ MzQ3MmVmMjU0OTBmOGNkYTA2YmQ3MmE2YjMwODY4YzFiMjBmNDFhYg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzhiMjcxNjEwMjZkYjZiYjIxMTRiM2FjNTc0ZmNhYTAzODBmZDc0NjZlOTMx
10
- ZWM2MDY4ZTcyYTIzODM4MzMzYWY3YzYzM2NlMGQ0YTUwZDAzYWY2NmM5MjIz
11
- ZDdhNWE3ODcwMTk2MDdmOTcxYTBiNjdiZjY3NjJmMThhM2U3NjI=
9
+ NzAxYTRmOWZmODlmZTgyOTY5MzE2MmNhM2VhMDllMjU0OTU5N2U2MWUyYzY0
10
+ NTUzYTgxNzU0YWQzMjQwYmNjOWI2MDU5OTk5MzI2MWNhMDVkNDZjZGNhMTI5
11
+ YTkyMjcxZWY5ODMyNTYxY2UzZmFhYTk0OGEwMTQ4ZjhmMDY2NWI=
12
12
  data.tar.gz: !binary |-
13
- ZDg5ZTg0N2IzZDA4NzA2NGM4Y2M3MTNhMzdkNDJkNmEyZWY0NzdlZmQxNmFh
14
- YjE2MjE1MGZlNzYxMjZiYzg5MDYyNjNmNDkzMzUwNDBlY2JlZDhjNzRiNDJk
15
- MzMyZWI1ZTY2YmFlNzYzMjBmYmRjYjNlYjY1MjcxZTQ4ODY5YTg=
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.parseDate(arg)
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
@@ -1,3 +1,3 @@
1
1
  module MotionModelResource
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.0"
3
3
  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.expand_path('../../motion/**/*.rb', __FILE__))).each do |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 = ["lshadyl@googlemail.com"]
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: :string,
35
- plan_id: :plan_id,
36
- email: :string,
37
- age: :integer,
38
- admin: :boolean
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: :string
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 '#parseValue' do
172
- it 'should parse a date in the right format for MotionModel' do
173
- time_string = "2013-11-03T16:59:35+01:00"
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
- MotionModelResource::DateParser.parseDate(time_string).should.equal("2013-11-03 15:59:35 +0000")
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
- it 'should return nil, when giving a wrong time' do
179
- time_string = "2342"
180
- time_string2 = "2013-21-03T16:59:35+01:00"
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
- MotionModelResource::DateParser.parseDate(time_string).should.equal nil
183
- MotionModelResource::DateParser.parseDate(time_string2).should.equal nil
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.1.9
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: 2013-12-29 00:00:00.000000000 Z
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
- - lshadyl@googlemail.com
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
@@ -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