pureapi 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 29f9ce1d05cb751dbf596027db202822a3673eaa
4
+ data.tar.gz: f3166e0119282f230f7250782153cf83a4bb884e
5
+ SHA512:
6
+ metadata.gz: c0e73f3029f3d785c960599c23cc6d71f5041044eaa9e80c205246bf1b100178fe2345bf5e91720cc467a92e310bb9e703bf8f88ed2ad3a33f3b55c2365e327a
7
+ data.tar.gz: a4b8f0a24ef52c14f16e34d884ca3b9a3447d2703c383d9cd32f558962ffaa7d0e652856a99b5fea6d1cc5ef20718bd65327119085de4288f085556dd1fbcf4d
data/bin/pureapi ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pureapi'
4
+ puts Pureapi.about(ARGV[0])
@@ -0,0 +1,484 @@
1
+ # Implement methods use for handle with table records like
2
+ # 1. Pagination
3
+ # 2. Sort
4
+ # 3. Search
5
+ # 4. Include
6
+ module Pureapi::Controller
7
+ # extend ActiveSupport::Concern
8
+
9
+ # Define constants of regex table and column name
10
+ FIELD_NAME = '[a-z_][a-z0-9_]*'
11
+ NAME_REGEX = /#{FIELD_NAME}/
12
+ FULLBEFORE_REGEX = /#{FIELD_NAME}\{/
13
+ BEFORE_REGEX = /\{/
14
+ AFTER_REGEX = /\}/
15
+
16
+ ### Begin 3 main API for GET data
17
+ # Filter list objects contain search, sort, paginate
18
+ def default_index_filter(objects)
19
+ # 1. search
20
+ # 2. sort and default sort is id.desc
21
+ objects = default_sort_filter(objects)
22
+ # 3. conditions
23
+ objects = default_cond_filter(objects)
24
+ objects = default_incond_filter(objects)
25
+ # 4. paginate
26
+ objects = paging_standard(objects)
27
+ end
28
+
29
+ # Filter list objects contain search, sort, limit
30
+ def default_report_filter(objects)
31
+ # 1. search
32
+ # 2. sort and default sort is id.desc
33
+ # 3. conditions
34
+ objects = default_cond_filter(objects)
35
+ objects = default_incond_filter(objects)
36
+ # 4. limit similar paginate
37
+ end
38
+
39
+ # Return json for index action
40
+ def records_json(criterias, includes_deep_level = 2)
41
+ options = {}
42
+ includes = criterias.default_includes
43
+
44
+ if params.permit(:fields)[:fields]
45
+ jsons = parse_includes(params.permit(:fields)[:fields], criterias, includes_deep_level)
46
+
47
+ includes += jsons[:includes] if !jsons[:includes].blank?
48
+ options = jsons[:as_json] if !jsons[:as_json].blank?
49
+ end
50
+
51
+ params[:fields] = deparse_fields(options)
52
+ options[:methods] = criterias.json_methods
53
+ options[:only] = options[:only].blank? ? criterias.default_onlyasjsons : options[:only]
54
+ criterias = criterias.includes(includes) if !includes.blank?
55
+ criterias.as_json(options)
56
+ end
57
+
58
+ # Return json for show action
59
+ def record_json(model, includes_deep_level = 2)
60
+ options = {}
61
+
62
+ if params.permit(:fields)[:fields]
63
+ jsons = parse_includes(params.permit(:fields)[:fields], model.class, includes_deep_level)
64
+
65
+ options = jsons[:as_json] if !jsons[:as_json].blank?
66
+ end
67
+
68
+ options[:only] = options[:only].blank? ? model.class.default_onlyasjsons : options[:only]
69
+ options[:methods] = model.class.json_methods
70
+ model.as_json(options)
71
+ end
72
+
73
+ # Return json for index action
74
+ def records_asjson(criterias)
75
+ options = {}
76
+ includes = []
77
+
78
+ if params.permit(:fields)[:fields]
79
+ jsons = parse_includes(params.permit(:fields)[:fields], criterias)
80
+
81
+ includes += jsons[:includes] if !jsons[:includes].blank?
82
+ options = jsons[:as_json] if !jsons[:as_json].blank?
83
+ end
84
+
85
+ params[:fields] = deparse_fields(options)
86
+ options[:only] = options[:only].blank? ? criterias.default_onlyasjsons : options[:only]
87
+ includes += criterias.json_method_includes.slice(*options[:methods]).values if !options[:methods].blank?
88
+
89
+ criterias.includes(includes.uniq).as_json(options)
90
+ end
91
+
92
+ # Return json for show action
93
+ def record_asjson(model)
94
+ options = {}
95
+
96
+ if params.permit(:fields)[:fields]
97
+ jsons = parse_includes(params.permit(:fields)[:fields], model.class)
98
+
99
+ options = jsons[:as_json] if !jsons[:as_json].blank?
100
+ end
101
+
102
+ options[:only] = options[:only].blank? ? model.class.default_onlyasjsons : options[:only]
103
+ model.as_json(options)
104
+ end
105
+
106
+ # Define json filters for params in render json api
107
+ def filter_jsons
108
+ {
109
+ paging: {
110
+ page: params[:page],
111
+ per_page: params[:per_page],
112
+ page_total: params[:page_total],
113
+ record_total: params[:record_total],
114
+ next: params[:page] < params[:page_total] ? (params[:page] + 1) : nil,
115
+ prev: params[:page] > 1 ? (params[:page] - 1) : nil,
116
+ },
117
+ orders: params[:orders] || [],
118
+ searches: params[:searches] || {},
119
+ compconds: params[:compconds] || {},
120
+ logics: params[:logics] || {},
121
+ fields: params[:fields] || ''
122
+ }
123
+ end
124
+
125
+ def report_filter_jsons
126
+ {
127
+ searches: params[:searches] || {},
128
+ compconds: params[:compconds] || {},
129
+ logics: params[:logics] || {},
130
+ }
131
+ end
132
+ ### End 3 main API for GET data
133
+
134
+ ### Begin options API for GET data
135
+ # Filter list objects contain search, sort, paginate
136
+ def standard_index_filter(objects)
137
+ # 1. search
138
+ objects = default_search_filter(objects)
139
+ # 2. sort and default sort is id.desc
140
+ objects = default_sort_filter(objects)
141
+ # 3. conditions
142
+ objects = default_cond_filter(objects)
143
+ objects = default_incond_filter(objects)
144
+ # 4. paginate
145
+ objects = paging_standard(objects)
146
+ end
147
+
148
+ # Filter list objects contain search, sort
149
+ def without_paging_index_filter(objects)
150
+ # 1. search
151
+ objects = default_search_filter(objects)
152
+ # 2. sort and default sort is id.desc
153
+ objects = default_sort_filter(objects)
154
+ # 3. conditions
155
+ objects = default_cond_filter(objects)
156
+ objects = default_incond_filter(objects)
157
+ end
158
+ ### End options API for GET data
159
+
160
+ ### Begin setup of default_search_filter
161
+ # Filter searchs of object criteria
162
+ def default_search_filter(objects)
163
+ params[:searches] = search_params
164
+ advanced_search_params.each{|k, v| params[:searches][k] = v}
165
+
166
+ objects = objects.full_search(search_params, advanced_search_params)
167
+ end
168
+ ### End setup of default_search_filter
169
+
170
+ ### Begin setup of default_sort_filter (.order asc|desc)
171
+ # Filter orders of object criteria
172
+ def default_sort_filter(objects)
173
+ orders = parse_orders(params.permit(orders: [])[:orders], objects.column_names)
174
+ orders[:id] = :desc if orders.blank?
175
+ params[:orders] = orders.map{|field, order_type| "#{field}.#{order_type}"}.uniq
176
+ objects.order(orders)
177
+ end
178
+
179
+ # array orders contains field.asc or field
180
+ # array column_names contains valid fields
181
+ # return hash of field => sort type (asc, desc)
182
+ def parse_orders(orders = [], column_names = [])
183
+ results = {}
184
+
185
+ orders.map do |order|
186
+ matchs = /(#{FIELD_NAME})\.(asc|desc)/.match(order) || /(#{FIELD_NAME})/i.match(order)
187
+ results[matchs[1]] = matchs[2] || 'asc' if matchs && column_names.include?(matchs[1])
188
+ end unless orders.blank?
189
+
190
+ results
191
+ end
192
+ ### End setup of default_sort_filter
193
+
194
+ ### Begin setup of feature comparison conditions
195
+ # Filter conditions of object criteria
196
+ # Need upgrade calculate compcond_params
197
+ def default_cond_filter(objects)
198
+ compcond_params = []
199
+
200
+ objects.compcond_columns.each do |column_name|
201
+ Constants::COMPARISON_OPERATORS.each do |key, value|
202
+ compcond_params << [column_name, key].join('.')
203
+ end
204
+ end
205
+
206
+ compconds = parse_compconds(params.permit(compconds: compcond_params)[:compconds].to_h, objects.column_names)
207
+ params[:compconds] = {}
208
+
209
+ compconds.each do |compcond|
210
+ params[:compconds]["#{compcond[:f]}.#{Constants::COMPARISON_OPERATORS_INVERT[compcond[:o]]}"] = compcond[:v]
211
+ end
212
+
213
+ objects.compconds(compconds)
214
+ end
215
+
216
+ # Not use under code because lte => lt
217
+ # matchs = /([a-z0-9_]+)\.(#{Constants::COMPARISON_OPERATORS.keys.join('|')})/.match(key)
218
+ # if matchs && column_names.include?(matchs[1])
219
+ # results << {f: matchs[1], o: Constants::COMPARISON_OPERATORS[matchs[2].to_sym], v: value}
220
+ # end
221
+ def parse_compconds(compconds = {}, column_names = [])
222
+ results = []
223
+
224
+ compconds.map do |key, value|
225
+ splits = key.split('.')
226
+
227
+ if splits.length == 2 && column_names.include?(splits[0])
228
+ if splits[1].to_sym == :eq && Constants::NULL_OPERATOR_KEYS.include?(value)
229
+ results << {
230
+ f: splits[0],
231
+ o: Constants::COMPARISON_OPERATORS[splits[1].to_sym],
232
+ v: Constants::NULL_OPERATORS[value]}
233
+ else
234
+ results << {f: splits[0], o: Constants::COMPARISON_OPERATORS[splits[1].to_sym], v: value}
235
+ end
236
+ end
237
+ end unless compconds.blank?
238
+
239
+ results
240
+ end
241
+ ### End setup of feature comparison conditions
242
+
243
+ ### Begin setup of feature in conditions (logical)
244
+ # Filter conditions of object criteria
245
+ # Need upgrade calculate compcond_params
246
+ def default_incond_filter(objects)
247
+ logic_params = []
248
+
249
+ objects.incond_columns.each do |column_name|
250
+ logic_params << {[column_name, :in].join('.') => []}
251
+ end
252
+
253
+ logics = parse_logicconds(params.permit(logics: logic_params)[:logics].to_h, objects.column_names)
254
+ params[:logics] = {}
255
+
256
+ logics.each do |logic|
257
+ params[:logics]["#{logic[:f]}.#{Constants::LOGICAL_OPERATORS_INVERT[logic[:o]]}"] = logic[:v]
258
+ end
259
+
260
+ objects.logicconds(logics)
261
+ end
262
+
263
+ # References from parse_compconds methods
264
+ # Upgrade results.push(!value.blank?) >> +parse_compconds+
265
+ def parse_logicconds(compconds = {}, column_names = [])
266
+ results = []
267
+
268
+ compconds.map do |key, value|
269
+ next if value.blank?
270
+
271
+ splits = key.split('.')
272
+
273
+ if splits.length == 2 && column_names.include?(splits[0])
274
+ results << {f: splits[0], o: Constants::LOGICAL_OPERATORS[splits[1].to_sym], v: value}
275
+ end
276
+ end unless compconds.blank?
277
+
278
+ results
279
+ end
280
+ ### End setup of feature comparison conditions
281
+
282
+ ### Begin setup of paging_standard. If not use will_paginate, write in mode class
283
+ # Method validate page number (like page params)
284
+ def validate_page(page, page_total)
285
+ page.blank? || page <= 0 ? 1 : (page > page_total ? page_total : page)
286
+ end
287
+
288
+ # Paging standard
289
+ # Criteria model that will be pagination
290
+ # Return criteria_model.paginate and params[:page, :per_page, :page_total]
291
+ def paging_standard(criteria_model)
292
+ per_page = [paging_params[:per_page].try(:to_i) || Constants::Pagination::DEFAULT_PER_PAGE, 1].max
293
+
294
+ # Paging standard
295
+ record_total = criteria_model.count
296
+ per_page = record_total if paging_params[:per_page] == Constants::Pagination::INFINITE_PER_PAGE\
297
+ && record_total > 0
298
+ page_total = (record_total.to_f / per_page).ceil
299
+ page = validate_page(paging_params[:page].try(:to_i) || 1, page_total)
300
+ page = [page, 1].max
301
+
302
+ # Assign paging params to params helper
303
+ params[:page] = page
304
+ params[:per_page] = per_page
305
+ params[:page_total] = page_total
306
+ params[:record_total] = record_total
307
+
308
+ criteria_model.paginate(page: page, per_page: per_page)
309
+ end
310
+ ### End setup of paging_standard
311
+
312
+ ### Begin feature records_json and record_json which render format json
313
+ # Methods return hash contain full params use for ActiveRecord includes()
314
+ # and ActiveModel::Serializers::JSON as_json()
315
+ def parse_includes(fields, model, includes_deep_level = 2)
316
+ _fields = parse_fields(fields, includes_deep_level)
317
+
318
+ if _fields[:only].is_a?(Array) && !_fields[:only].blank?
319
+ _fields[:methods] = _fields[:only] & model.json_methods
320
+ _fields[:only] = _fields[:only] & model.column_names.map(&:to_sym)
321
+ end
322
+
323
+ if _fields[:include].is_a?(Hash) && !_fields[:include].blank?
324
+ _fields[:include] = merge_includes(_fields[:include], model.asjson_fields)
325
+ end
326
+
327
+ {
328
+ includes: include_relations(_fields),
329
+ as_json: _fields.delete_if { |k, v| v.blank? }
330
+ }
331
+ end
332
+
333
+ # Methods parse string fields user for as_json
334
+ # Example str = "a,b, c,d,e,f,g{k,l,h{b,i}},w{n, m},j,t{ww, gh, l{bb}}, f{k, mm}, vv"
335
+ # return result = {:only=>["a", "b", "c", "d", "e", "f", "j", "vv"],
336
+ # :include=>{"g"=>{:only=>["k", "l"], :include=>{"h"=>{:only=>["b", "i"]}}},
337
+ # "w"=>{:only=>["n", "m"]}, "t"=>{:only=>["ww", "gh"], :include=>{"l"=>{:only=>["bb"]}}},
338
+ # "f"=>{:only=>["k", "mm"]}}}
339
+ def parse_fields(str, deep = 2)
340
+ result = { only: [], include: {} }
341
+
342
+ fullbefore = substr_index(str, FULLBEFORE_REGEX)
343
+ after = substr_index(str, AFTER_REGEX)
344
+
345
+ if fullbefore.length == after.length
346
+ if fullbefore.length == 0
347
+ result[:only] = str.scan(NAME_REGEX)
348
+ else
349
+ kvs = openandclose(fullbefore, after)
350
+ _kvs = openandclose(substr_index(str, BEFORE_REGEX), after).invert
351
+ s = 0
352
+
353
+ kvs.each do |k, v|
354
+ if s < k
355
+ result[:only] += str[s..(k - 1)].scan(NAME_REGEX)
356
+ s = v + 1
357
+ elsif
358
+ s = v + 1
359
+ end
360
+
361
+ if k < v && deep > 0
362
+ result[:include][str[k..v][NAME_REGEX].to_sym] =\
363
+ parse_fields(str[(_kvs[v] + 1)..(v - 1)], deep - 1)
364
+ end
365
+ end
366
+
367
+ if s < (str.length - 1)
368
+ result[:only] += str[s..-1].scan(NAME_REGEX)
369
+ end
370
+ end
371
+ end
372
+
373
+ result[:only] = result[:only].map(&:to_sym)
374
+ return result.delete_if { |k, v| v.blank? }
375
+ end
376
+
377
+ # Hash +fields+
378
+ # return String is params query
379
+ def deparse_fields(fields)
380
+ return '' unless fields.is_a? Hash
381
+
382
+ _arr = []
383
+
384
+ if fields[:only].is_a? Array
385
+ _arr += fields[:only]
386
+ end
387
+
388
+ if fields[:methods].is_a? Array
389
+ _arr += fields[:methods]
390
+ end
391
+
392
+ if fields[:include].is_a?(Hash) && !fields[:include].blank?
393
+ fields[:include].each do |k, v|
394
+ _arr << "#{k}{#{deparse_fields(v)}}"
395
+ end
396
+ end
397
+
398
+ _arr.join(',')
399
+ end
400
+
401
+ # Require length of before and after is equal
402
+ # +before.length == after.length > 1+
403
+ def openandclose(before, after)
404
+ results = {}
405
+
406
+ index = 0
407
+ results[before[index]] = after[index]
408
+ before.each_with_index do |value, i|
409
+ if results[before[index]] > value
410
+ results[before[index]] = after[i]
411
+ else
412
+ index = i
413
+ results[before[index]] = after[index]
414
+ end
415
+ end
416
+
417
+ return results
418
+ end
419
+
420
+ # return Array all index of sub_str in str
421
+ def substr_index(str, sub_str)
422
+ i = 0
423
+ all = []
424
+ while (i = str.index(sub_str, i)).is_a? Integer
425
+ all << i
426
+ i += str[i..-1][sub_str].length
427
+ end
428
+ all
429
+ end
430
+
431
+ # return all relations be included by fields
432
+ def include_relations(fields)
433
+ result = []
434
+
435
+ if fields[:include].is_a? Hash
436
+ fields[:include].each do |k, v|
437
+ if (v.is_a? Hash) && (v[:include].is_a? Hash)
438
+ result << { k => include_relations(v)}
439
+ else
440
+ result << k
441
+ end
442
+ end
443
+ end
444
+
445
+ result
446
+ end
447
+
448
+ def merge_includes(target ,source)
449
+ result = {}
450
+
451
+ target.each do |k, v|
452
+ result[k] = {}
453
+
454
+ if v.is_a?(Hash) && source[k].is_a?(Hash)
455
+ if source[k][:only].is_a?(Array)
456
+ result[k][:only] = v[:only].is_a?(Array) ? (v[:only] & source[k][:only]) : source[k][:only]
457
+ end
458
+
459
+ if v[:include].is_a?(Hash) && source[k][:include].is_a?(Hash)
460
+ result[k][:include] = merge_includes(v[:include], source[k][:include])
461
+ end
462
+ end
463
+ end
464
+
465
+ result.delete_if { |k, v| v.blank? }
466
+ end
467
+ ### End feature records_json and record_json
468
+
469
+ ### Begin declare strong parameters
470
+ def paging_params
471
+ params.permit(:page, :per_page)
472
+ end
473
+
474
+ # Strong parameters for default search query
475
+ def search_params
476
+ params.permit()
477
+ end
478
+
479
+ # Strong parameters for default advanced search query
480
+ def advanced_search_params
481
+ params.permit()
482
+ end
483
+ ### End declare strong parameters
484
+ end
@@ -0,0 +1,128 @@
1
+ # Implement methods use for handle with table records like
2
+ # 1. Methods in as_json
3
+ # 2.
4
+ module Pureapi::Model
5
+ # return default array contains methods for as_json
6
+ # Eg. [:status_name, :path_detail, :restrictions, :channel_course_code]
7
+ def json_methods
8
+ []
9
+ end
10
+
11
+ # return default criterias for advanced_search
12
+ # Eg.
13
+ # User .where({}) to fix case return self
14
+ # def advanced_search(advanced_params)
15
+ # criterias = self
16
+
17
+ # # search by list course_ids
18
+ # criterias = advanced_params[:course_ids].blank? ?
19
+ # criterias : criterias.where(course_id: advanced_params[:course_ids])
20
+
21
+ # # search by list channel_ids
22
+ # criterias = advanced_params[:channel_ids].blank? ?
23
+ # criterias : criterias.where(channel_id: advanced_params[:channel_ids])
24
+
25
+ # # search by code
26
+ # criterias = advanced_params[:code].blank? ?
27
+ # criterias : criterias.where(
28
+ # "`channel_courses`.`code` LIKE ?",
29
+ # "%#{advanced_params[:code]}%"
30
+ # )
31
+
32
+ # return criterias.where({})
33
+ # end
34
+ def advanced_search(advanced_params)
35
+ return self.where({})
36
+ end
37
+
38
+ # Provide search function 2 type: normal and advanced
39
+ # Required setup advanced_search methods
40
+ #
41
+ # ActionController::Parameters +_params+, contain fields use .where()
42
+ # ActionController::Parameters +_advanced_params+, detail in advanced_search method
43
+ # Or
44
+ # Hash +_params+, contain fields use .where()
45
+ # Hash +_advanced_params+, detail in advanced_search method
46
+ def full_search(params, advanced_params)
47
+ _params = params.delete_if { |k, v| v.blank? }
48
+
49
+ criterias = self
50
+ criterias = _params.blank? ? criterias : criterias.where(_params.to_h)
51
+ criterias = advanced_params.blank? ? criterias : criterias.advanced_search(advanced_params.to_h)
52
+
53
+ return criterias.where({})
54
+ end
55
+
56
+ # Comparsion operator conditions method
57
+ # Array +params+ contains hash = {f, o, v}
58
+ # :f is column name
59
+ # :o is comparsion operator
60
+ # :v is value
61
+ def compconds(params = [])
62
+ criterias = self
63
+
64
+ params.each do |param|
65
+ if param[:o] == '='
66
+ criterias = criterias.where(param[:f] => param[:v])
67
+ else
68
+ criterias = criterias.where("`#{self.table_name}`.`#{param[:f]}` #{param[:o]} ?", param[:v])
69
+ end
70
+ end
71
+
72
+ return criterias.where({})
73
+ end
74
+
75
+ # Comparsion operator conditions method
76
+ # Array +params+ contains hash = {f, o, v}
77
+ # :f is column name
78
+ # :o is comparsion operator
79
+ # :v is value
80
+ def logicconds(params = [])
81
+ criterias = self
82
+
83
+ params.each do |param|
84
+ if param[:o] == 'IN'
85
+ criterias = criterias.where(param[:f] => param[:v])
86
+ end
87
+ end
88
+
89
+ return criterias.where({})
90
+ end
91
+
92
+ # Define array of columns use for comparison conditions
93
+ # E.g [:id, :name, :channel_course_id, :status]
94
+ def compcond_columns
95
+ [:id]
96
+ end
97
+
98
+ # Define array of columns use for in conditions (logical)
99
+ # E.g [:id, :name, :channel_course_id, :status]
100
+ def incond_columns
101
+ [:id]
102
+ end
103
+
104
+ # Define default includes of active records
105
+ # E.g [:channel, :course]
106
+ def default_includes
107
+ []
108
+ end
109
+
110
+ # Define default fields use for asjson on includes
111
+ # E.g
112
+ # {
113
+ # channel: {only: [:id, :code, :name], include: Channel.asjson_fields},
114
+ # course: {only: [:id, :code, :name]}
115
+ # }
116
+ def asjson_fields
117
+ {}
118
+ end
119
+
120
+ def json_method_includes
121
+ {}
122
+ end
123
+
124
+ # Define default fields use for as_jsons
125
+ def default_onlyasjsons
126
+ self.column_names.map(&:to_sym)
127
+ end
128
+ end
data/lib/pureapi.rb ADDED
@@ -0,0 +1,16 @@
1
+ class Pureapi
2
+ class << self
3
+ # Say hi to you & talk about gem!
4
+ #
5
+ # Example:
6
+ # >> Pureapi.about("spanish")
7
+ # => hello spanish
8
+ #
9
+ # Arguments:
10
+ # yourname: (String)
11
+
12
+ def about yourname
13
+ "hello #{yourname}. This is a library for pure api"
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pureapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dieu Pham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple concern in model & controller for api
14
+ email: dieupv@topica.edu.vn
15
+ executables:
16
+ - pureapi
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/pureapi
21
+ - lib/pureapi.rb
22
+ - lib/pureapi/controller.rb
23
+ - lib/pureapi/model.rb
24
+ homepage: http://rubygems.org/gems/pureapi
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.6.8
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Pure API
48
+ test_files: []