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.
- 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
|