traim 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/lib/traim.rb +144 -66
- data/test/resource.rb +83 -5
- data/traim.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7f9d2dc6a750a774b11b5d3902146d3a5b7cbb7
|
4
|
+
data.tar.gz: fa2d4cb1d990e357481b137d9c1797c6f25ffe3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
```
|
data/lib/traim.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
100
|
-
render(request,
|
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
|
-
|
100
|
+
controller = nil
|
106
101
|
begin
|
107
102
|
segment = inbox[:segment].to_sym
|
108
103
|
|
109
|
-
if
|
104
|
+
if controller.nil?
|
110
105
|
raise BadRequestError unless @resource = @resources[segment]
|
111
|
-
|
106
|
+
controller = @resource.build_controller(request)
|
112
107
|
next
|
113
108
|
end
|
114
109
|
|
115
|
-
if
|
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
|
-
|
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
|
-
|
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
|
-
|
135
|
+
create_record(params)
|
149
136
|
end
|
150
137
|
actions["GET"] = lambda do
|
151
|
-
|
138
|
+
find_record(id)
|
152
139
|
end
|
153
140
|
actions["PUT"] = lambda do
|
154
|
-
result =
|
141
|
+
result = update_record(id, params)
|
155
142
|
result
|
156
143
|
end
|
157
144
|
actions["DELETE"] = lambda do
|
158
|
-
|
145
|
+
delete_record(id)
|
159
146
|
end
|
160
147
|
actions
|
161
148
|
end
|
162
149
|
end
|
163
150
|
|
164
|
-
def to_json(resources,
|
165
|
-
if
|
166
|
-
hash =
|
167
|
-
@resource.to_hash(
|
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
|
173
|
-
new_hash = @resource.to_hash(
|
159
|
+
if controller.errors.size == 0
|
160
|
+
new_hash = @resource.to_hash(controller, resources)
|
174
161
|
else
|
175
|
-
new_hash =
|
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,
|
182
|
-
method_block =
|
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
|
-
|
191
|
-
|
192
|
-
[
|
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
|
293
|
-
|
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
|
-
|
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
|
-
|
300
|
+
controller.attributes[name.to_s]
|
300
301
|
else
|
301
|
-
|
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
|
-
|
307
|
-
|
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[
|
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
|
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 =
|
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
|
397
|
+
def record=(record); @record = record end
|
398
|
+
def record; @record ||= find_record(id) end
|
355
399
|
|
356
|
-
def
|
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
|
406
|
+
def find_record(id)
|
363
407
|
@model.find id
|
364
408
|
end
|
365
409
|
|
366
|
-
def
|
367
|
-
|
410
|
+
def delete_record(id)
|
411
|
+
find_record(id).delete
|
368
412
|
end
|
369
413
|
|
370
|
-
def
|
371
|
-
|
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
|
data/test/resource.rb
CHANGED
@@ -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.
|
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 "
|
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
|
data/traim.gemspec
CHANGED
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.
|
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-
|
11
|
+
date: 2018-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|