pureapi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []