traim 0.2.1 → 0.3.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.
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