traim 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -1
  3. data/lib/traim.rb +144 -66
  4. data/test/resource.rb +83 -5
  5. data/traim.gemspec +1 -1
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f204ffc2cdf698726f079e91eb5db19350b444d
4
- data.tar.gz: 47425e6be45b76c807203f0ccf9aa44634286769
3
+ metadata.gz: c7f9d2dc6a750a774b11b5d3902146d3a5b7cbb7
4
+ data.tar.gz: fa2d4cb1d990e357481b137d9c1797c6f25ffe3f
5
5
  SHA512:
6
- metadata.gz: 0aae8da56d68a5445902b91a94127360fb6b4729d494a049d497af3dde6e0ac9f327400bf60de975a61e4556dbc67f7767b1684290505d3aa81107c082875c20
7
- data.tar.gz: 6262dd3d799c091b151aa341ac5c77fa901b76313b529a5b06714f1b6c7e756225766512e69f3c11cbf0dcc486157a1005b19bf8c0d2d6668ed9e0cf2c72ad3d
6
+ metadata.gz: 042f77d48d944dad9f3c3bd641a942efe5343aa96b890f37169eca4fc8a772bbef47a643a1ac8bf76fb9c315203e06ee7125739fd2cfdcbf872d63ae890b7b2d
7
+ data.tar.gz: d69f5b48785ea17cf80b6c62260ad75371b6d68a742d2558551239c1d4fec7449c8959a4162086bed8ae29530adf4e2ff594593f9f0455d388a16349f6b8bdb5
data/README.md CHANGED
@@ -67,13 +67,18 @@ Traim.application do
67
67
  model User
68
68
 
69
69
  attribute :id
70
- attribute :name
71
70
 
72
71
  action :show do
72
+
73
+ # attribute for single action
74
+ attribute :name
75
+
73
76
  user = model.find_by_email params["email"]
74
77
  user.name = "[admin] #{user.name}"
75
78
  user
76
79
  end
80
+
81
+ action :create
77
82
  end
78
83
  end
79
84
  ```
@@ -65,7 +65,7 @@ class Traim
65
65
  end
66
66
 
67
67
  def resources(name, &block)
68
- @resources[name] = Resource.new(block)
68
+ @resources[name] = Resource.new(block, @helper)
69
69
  end
70
70
 
71
71
  def namespace(name, &block)
@@ -78,13 +78,8 @@ class Traim
78
78
  app = @applications[name] ||= Application.new(name)
79
79
  end
80
80
 
81
- def show(&block); @resource.action(:show, &block) end
82
- def create(&block); @resource.action(:create, &block) end
83
- def update(&block); @resource.action(:update, &block) end
84
- def destory(&block); @resource.action(:destory, &block) end
85
-
86
81
  def helpers(&block)
87
- @helpers_block = block
82
+ @helper = Helper.new(block)
88
83
  end
89
84
 
90
85
  def route(request, seg = nil)
@@ -96,90 +91,83 @@ class Traim
96
91
  if app = @applications[segment]
97
92
  app.route(request, seg)
98
93
  else
99
- model = run(seg, inbox, request)
100
- render(request, model)
94
+ controller = run(seg, inbox, request)
95
+ render(request, controller)
101
96
  end
102
97
  end
103
98
 
104
99
  def run(seg, inbox, request)
105
- model = nil
100
+ controller = nil
106
101
  begin
107
102
  segment = inbox[:segment].to_sym
108
103
 
109
- if model.nil?
104
+ if controller.nil?
110
105
  raise BadRequestError unless @resource = @resources[segment]
111
- model = Model.new(@resource.model, request)
106
+ controller = @resource.build_controller(request)
112
107
  next
113
108
  end
114
109
 
115
- if model.id.nil?
110
+ if controller.id.nil?
116
111
  if collection = @resource.collections[segment]
117
- instance_eval(&collection)
112
+ controller.instance_eval(&collection)
118
113
  break
119
114
  else
120
- model.id = segment.to_s.to_i
115
+ controller.id = segment.to_s.to_i
121
116
  next
122
117
  end
123
118
  end
124
119
 
125
120
  if member = @resource.members[segment]
126
- instance_eval(&member)
121
+ controller.instance_eval(&member)
127
122
  break
128
123
  end
129
124
 
130
125
  raise BadRequestError
131
126
  end while seg.capture(:segment, inbox)
132
127
 
133
- model.instance_eval(&@helpers_block) unless @helpers_block.nil?
134
- model
128
+ controller
135
129
  end
136
130
 
137
- def action(name)
138
- raise NotImplementedError unless action = @resource.actions[name]
139
-
140
- action[:block] = default_actions[name] if action[:block].nil?
141
- action
142
- end
143
-
144
131
  def default_actions
145
132
  @default_actions ||= begin
146
133
  actions = {}
147
134
  actions["POST"] = lambda do
148
- create(params)
135
+ create_record(params)
149
136
  end
150
137
  actions["GET"] = lambda do
151
- show(id)
138
+ find_record(id)
152
139
  end
153
140
  actions["PUT"] = lambda do
154
- result = update(id, params)
141
+ result = update_record(id, params)
155
142
  result
156
143
  end
157
144
  actions["DELETE"] = lambda do
158
- delete(id)
145
+ delete_record(id)
159
146
  end
160
147
  actions
161
148
  end
162
149
  end
163
150
 
164
- def to_json(resources, model)
165
- if @result.kind_of?(ActiveRecord::Relation)
166
- hash = @result.map do |object|
167
- @resource.to_hash(object, resources, model)
151
+ def to_json(resources, controller)
152
+ if controller.is_collection?
153
+ hash = controller.map_record do |object, controller|
154
+ @resource.to_hash(controller, resources)
168
155
  end
169
156
  JSON.dump(hash)
170
157
  else
171
158
  new_hash = {}
172
- if @result.errors.size == 0
173
- new_hash = @resource.to_hash(@result, resources, model)
159
+ if controller.errors.size == 0
160
+ new_hash = @resource.to_hash(controller, resources)
174
161
  else
175
- new_hash = @result.errors.messages
162
+ new_hash = controller.errors.messages
176
163
  end
177
164
  JSON.dump(new_hash)
178
165
  end
179
166
  end
180
167
 
181
- def render(request, model)
182
- method_block = action(request.request_method)
168
+ def render(request, controller)
169
+ raise NotImplementedError unless method_block = controller.actions(request.request_method)
170
+ method_block[:block] = default_actions[request.request_method] if method_block[:block].nil?
183
171
 
184
172
  if (method_block[:options][:permit])
185
173
  if not_permmited_payload = request.params.detect { |key, value| !method_block[:options][:permit].include?(key) }
@@ -187,9 +175,9 @@ class Traim
187
175
  end
188
176
  end
189
177
 
190
- model.params = request.params
191
- @result = model.instance_eval(&method_block[:block])
192
- [model.status, model.headers, [to_json(@resources, model)]]
178
+ controller.params = request.params
179
+ controller.execute(method_block[:block])
180
+ [controller.status, controller.headers, [to_json(@resources, controller)]]
193
181
  end
194
182
 
195
183
  def compile(&block)
@@ -243,8 +231,9 @@ class Traim
243
231
  ACTION_METHODS = {create: 'POST', show: 'GET', update: 'PUT', destory: 'DELETE'}
244
232
 
245
233
 
246
- def initialize(block)
234
+ def initialize(block, helper)
247
235
  instance_eval(&block)
236
+ @helper = helper
248
237
  end
249
238
 
250
239
  def model(object = nil, options = {})
@@ -252,6 +241,11 @@ class Traim
252
241
  @model
253
242
  end
254
243
 
244
+ def controller(object = nil)
245
+ @controller = object unless object.nil?
246
+ @controller || ArController
247
+ end
248
+
255
249
  def actions; @actions ||= {} end
256
250
  def action(name, options = {}, &block)
257
251
  actions[ACTION_METHODS[name]] = {block: block, options: options}
@@ -281,57 +275,88 @@ class Traim
281
275
  fields << {name: name, type: 'attribute', block: block}
282
276
  end
283
277
 
284
- def has_many(name)
285
- fields << {name: name, type: 'association'}
278
+ def has_many(name, options={}, &block)
279
+ fields << {name: name, type: 'association', resource: options[:resource], block: block}
286
280
  end
287
281
 
288
- def has_one(name)
289
- fields << {name: name, type: 'connection'}
282
+ def has_one(name, options={}, &block)
283
+ fields << {name: name, type: 'connection', resource: options[:resource], block: block}
290
284
  end
291
285
 
292
- def to_hash(object, resources, model, nested_associations = [])
293
- return if object.nil?
286
+ def build_controller(request)
287
+ controller.new(model, actions.dup, @helper.clone, request)
288
+ end
289
+
290
+ def to_hash(controller, resources, nested_associations = [])
291
+ return if controller.nil?
294
292
 
295
- fields.inject({}) do | hash, attr|
293
+ hash_fields = fields
294
+ hash_fields += controller.fields if controller.respond_to? :fields
295
+ hash_fields.inject({}) do | hash, attr|
296
296
  name = attr[:name]
297
+
297
298
  hash[name] = if attr[:type] == 'attribute'
298
299
  if attr[:block].nil?
299
- object.attributes[name.to_s]
300
+ controller.attributes[name.to_s]
300
301
  else
301
- model.instance_exec(object, &attr[:block])
302
+ attribute_controller = build_controller(controller.request)
303
+ attribute_controller.record = controller.record
304
+ attribute_controller.execute(attr[:block])
302
305
  end
303
306
  elsif attr[:type] == 'association'
304
307
  raise Error if nested_associations.include?(name)
305
308
  nested_associations << name
306
- object.send(name).map do |association|
307
- resources[name].to_hash(association, resources, model, nested_associations.dup)
309
+ resource = resources[attr[:resource] || name]
310
+ associated_controller = resource.build_controller(controller.request)
311
+ associated_controller.record = if attr[:block].nil?
312
+ controller.send(name)
313
+ else
314
+ controller.dup.execute(attr[:block])
315
+ end
316
+ associated_controller.map_record do |item, controller|
317
+ resource.to_hash(controller, resources, nested_associations.dup)
308
318
  end
309
319
  else
310
320
  resource_name = name.to_s.pluralize.to_sym
311
321
  raise Error.new(message: "Inifinite Association") if nested_associations.include?(resource_name)
312
- raise Error if object.class.reflections[name.to_s].blank?
313
322
  nested_associations << resource_name
314
- resources[resource_name].to_hash(object.send(name), resources, model, nested_associations.dup)
323
+ resource = resources[attr[:resource] || resource_name]
324
+ connected_controller = resource.build_controller(controller.request)
325
+ connected_controller.record = if attr[:block].nil?
326
+ controller.send(name)
327
+ else
328
+ controller.dup.execute(attr[:block])
329
+ end
330
+ resource.to_hash(connected_controller, resources, nested_associations.dup)
315
331
  end
316
332
  hash
317
333
  end
318
334
  end
319
335
  end
320
336
 
321
- class Model
337
+ class Controller
322
338
 
323
- def initialize(model, request = nil)
339
+ def initialize(model, actions, helper = nil, request = nil)
324
340
  @model = model
325
341
  @request = request
326
342
  @headers = {}
343
+ @actions = actions
344
+
327
345
  ok
346
+
347
+ unless helper.nil?
348
+ @helper = helper
349
+ @helper.request = @request
350
+ @helper.status = @status
351
+ @helper.model = @model
352
+ end
328
353
  end
329
354
 
330
355
  attr_accessor :id
331
356
  attr_accessor :model
332
- attr_accessor :record
333
357
  attr_accessor :params
334
358
  attr_accessor :request
359
+ attr_accessor :helper
335
360
  attr_accessor :status
336
361
 
337
362
  def logger; Traim.logger end
@@ -341,9 +366,27 @@ class Traim
341
366
  def created; @status = 201 end
342
367
  def no_cotent; @status = 204 end
343
368
 
369
+ def show(options = {}, &block); @actions["GET"] = {block: block, options: options} end
370
+ def create(options = {}, &block); @actions["POST"] = {block: block, options: options} end
371
+ def update(options = {}, &block); @actions["PUT"] = {block: block, options: options} end
372
+ def destory(options = {}, &block); @actions["DELETE"] = {block: block, options: options} end
373
+
374
+ def actions(name); @actions[name] end
375
+
376
+ def fields; @fields ||= [] end
377
+ def attribute(name, &block)
378
+ fields << {name: name, type: 'attribute', block: block}
379
+ end
380
+ def has_many(name, options={}, &block)
381
+ fields << {name: name, type: 'association', resource: options[:resource], block: block}
382
+ end
383
+ def has_one(name, options={}, &block)
384
+ fields << {name: name, type: 'connection', resource: options[:resource], block: block}
385
+ end
386
+
344
387
  def instance_record(id)
345
388
  @id = id
346
- @record = show(@id)
389
+ @record = find_record(@id)
347
390
  end
348
391
 
349
392
  def headers(key = nil, value = nil)
@@ -351,27 +394,62 @@ class Traim
351
394
  @headers[key] = value
352
395
  end
353
396
 
354
- def record; @record ||= show(id) end
397
+ def record=(record); @record = record end
398
+ def record; @record ||= find_record(id) end
355
399
 
356
- def create(params)
400
+ def create_record(params)
357
401
  resource = @model.new(params)
358
402
  resource.save
359
403
  resource
360
404
  end
361
405
 
362
- def show(id)
406
+ def find_record(id)
363
407
  @model.find id
364
408
  end
365
409
 
366
- def delete(id)
367
- show(id).delete
410
+ def delete_record(id)
411
+ find_record(id).delete
368
412
  end
369
413
 
370
- def update(id, params)
371
- resource = show(id)
414
+ def method_missing(m, *args, &block)
415
+ record.send(m, *args, &block)
416
+ end
417
+
418
+ def update_record(id, params)
419
+ resource = find_record(id)
372
420
  resource.assign_attributes(params)
373
421
  resource.save
374
422
  resource
375
423
  end
424
+
425
+ def is_collection?; record.is_a?(Enumerable) end
426
+
427
+ def map_record
428
+ @record.map do |item|
429
+ new_controller = self.class.new(@model, @actions)
430
+ new_controller.record = item
431
+ yield item, new_controller
432
+ end
433
+ end
434
+
435
+ def execute(block)
436
+ @record = instance_eval(&block)
437
+ end
438
+ end
439
+
440
+ # A controller design for ActiveRecord
441
+ class ArController < Controller
442
+ end
443
+
444
+ class Helper
445
+ def initialize(block)
446
+ instance_eval(&block)
447
+ end
448
+ def logger; Traim.logger end
449
+
450
+ attr_accessor :request
451
+ attr_accessor :status
452
+ attr_accessor :model
453
+
376
454
  end
377
455
  end
@@ -10,6 +10,10 @@ class User < ActiveRecord::Base
10
10
  validates_presence_of :name
11
11
 
12
12
  has_many :books
13
+
14
+ def limited_books
15
+ books.limit(2)
16
+ end
13
17
  end
14
18
 
15
19
  class Book < ActiveRecord::Base
@@ -95,8 +99,6 @@ test "member create, read, update and destory functionality" do |user|
95
99
 
96
100
  member :blurred do
97
101
  show do
98
- logger.info("show blurred")
99
- logger.info("class #{self.class.name}")
100
102
  record.name[1..2] = "xx"
101
103
  record
102
104
  end
@@ -199,7 +201,7 @@ test "has many functionality" do |user|
199
201
 
200
202
  create do
201
203
  # what to response?
202
- model.create(params)
204
+ model.create_record(params)
203
205
  end
204
206
  end
205
207
  end
@@ -269,7 +271,7 @@ test "namespace functionality" do |user|
269
271
  assert result["user"]["name"] == user.name
270
272
  end
271
273
 
272
- test "visual attributes functionality" do |user|
274
+ test "virtual attributes functionality" do |user|
273
275
  app = Traim.application do
274
276
  resources :users do
275
277
  model User
@@ -290,6 +292,47 @@ test "visual attributes functionality" do |user|
290
292
  assert result["name"] == "kolo"
291
293
  end
292
294
 
295
+ test "attributes in action" do |user|
296
+ book = Book.create(user: user, isbn: 'abc')
297
+ app = Traim.application do
298
+ resources :users do
299
+ model User
300
+
301
+ attribute :id
302
+
303
+ action :show do
304
+ attribute :name
305
+ record
306
+ end
307
+
308
+ member :books do
309
+ show do
310
+ has_many :books
311
+
312
+ record
313
+ end
314
+ end
315
+ end
316
+
317
+ resources :books do
318
+ model Book
319
+ attribute :isbn
320
+
321
+ has_one :user
322
+
323
+ action :show
324
+ end
325
+ end
326
+
327
+ _, _, response = mock_request(app, "/users/#{user.id}", "GET")
328
+ result = JSON.parse(response.first)
329
+ assert result["name"] == "kolo"
330
+
331
+ _, _, response = mock_request(app, "/users/#{user.id}/books", "GET")
332
+ result = JSON.parse(response.first)
333
+ assert result["books"].first["isbn"] == book.isbn
334
+ end
335
+
293
336
  test "strong parameters functionality" do |user|
294
337
  app = Traim.application do
295
338
  resources :users do
@@ -323,7 +366,7 @@ test "helpers functionality" do |user|
323
366
 
324
367
  member :blurred do
325
368
  show do
326
- auth('test')
369
+ helper.auth('test')
327
370
  record.name[1..2] = "xx"
328
371
  record
329
372
  end
@@ -374,6 +417,41 @@ test "headers functionality" do |user|
374
417
  assert headers["test"] == "yeah"
375
418
  end
376
419
 
420
+ test "custom resource for association" do |user|
421
+ Book.create(user: user, isbn: 'isbn_1')
422
+ Book.create(user: user, isbn: 'isbn_2')
423
+ Book.create(user: user, isbn: 'isbn_3')
424
+
425
+ app = Traim.application do
426
+ resources :users do
427
+ model User
428
+
429
+ attribute :id
430
+
431
+ action :show
432
+
433
+ has_many :limited_books, resource: :books do
434
+ record.limited_books
435
+ end
436
+
437
+ has_one :second_book, resource: :books do
438
+ record.books.second
439
+ end
440
+ end
441
+
442
+ resources :books do
443
+ model Book
444
+
445
+ attribute :isbn
446
+ end
447
+ end
448
+
449
+ _, headers, response = mock_request(app, "/users/#{user.id}", "GET")
450
+ result = JSON.parse(response.first)
451
+ assert result["limited_books"].length == 2
452
+ assert result["second_book"]["isbn"] == "isbn_2"
453
+ end
454
+
377
455
 
378
456
  Book.delete_all
379
457
  User.delete_all
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "traim"
3
- s.version = "0.2.1"
3
+ s.version = "0.3.0"
4
4
  s.summary = %{Resource-oriented microframework for RESTful API}
5
5
  s.description = %Q{Resource-oriented microframework for RESTful API}
6
6
  s.authors = ["Travis Liu"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: traim
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Travis Liu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-14 00:00:00.000000000 Z
11
+ date: 2018-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack