motion_model_resource 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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