crud-service 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjQyZGYyOWQ0YWY1YjIyODkyNWQ1OTI5NGNkYzI4ZDA2OGUxZmE1Ng==
5
+ data.tar.gz: !binary |-
6
+ NmYxYjViMGZhMWM3OWVkMTlkYWQ1NWIxZmZkYTBjNzA1YTVmZGJhYw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZWMxNDdiMmY4YmY1YjlmNzJjNjZjYWE4NzhhN2U4Zjc1NTI0ZTI5NjYzZjVk
10
+ MGQzYmUzNDhjM2E4NGRhNDRjNDExNTYwY2I0ZTU1YzM4ZTk0NDM1MWYzNWNl
11
+ YWQ4MTZjNWZlODkyNDY1YTA4ZGJjMDBhYmM5YjUxMDBiODIyM2U=
12
+ data.tar.gz: !binary |-
13
+ MzgyODJiNDFmMmYwN2YzODE5YzY1OTdhMzAzMGUzMDliYWYxYWRkNzg0N2Mz
14
+ OWRiNDRhOGYwMWYxNDZkODRmZjA4NTMzZmRhMDViN2JjYWU2NTcyNDI0NDY2
15
+ N2NiZWZmODIzMzIwY2Q2MzkwNzYwM2IzYTdhMDE4ZmVlYTA1MTg=
@@ -0,0 +1,87 @@
1
+ module CrudService
2
+ class GenericApi
3
+
4
+ def self.crud_api(sinatra, service_name, name, primary_key_name)
5
+
6
+ sinatra.options '/'+name do
7
+ 204
8
+ end
9
+
10
+ sinatra.post '/'+name do
11
+ # Get The data
12
+ begin
13
+ data = JSON.parse(request.body.read)
14
+ rescue Exception => e
15
+ return 400
16
+ end
17
+
18
+ # Valid POST?
19
+ return 400 unless settings.send(service_name).valid_insert?(data)
20
+
21
+ # Already Exists?
22
+ return 409 if settings.send(service_name).exists_by_primary_key?(data['code'])
23
+
24
+ # Do Insert
25
+ record = settings.send(service_name).insert(data)
26
+
27
+ # Other Error
28
+ return 500 if record == false
29
+
30
+ # Output new record
31
+ JSON.fast_generate record
32
+ end
33
+
34
+ sinatra.get '/'+name do
35
+ sanitize_params(params)
36
+ # Check query validity
37
+ return 400 unless settings.send(service_name).valid_query?(params)
38
+
39
+ # Return Regions on Query
40
+ JSON.fast_generate settings.send(service_name).get_all_by_query(params)
41
+ end
42
+
43
+ sinatra.get '/'+name+'/:'+primary_key_name do
44
+ sanitize_params(params)
45
+ return 400 unless settings.send(service_name).valid_query?(params)
46
+
47
+ record = settings.send(service_name).get_one_by_query(params)
48
+ return 404 if record.nil?
49
+ JSON.fast_generate record
50
+ end
51
+
52
+ sinatra.put '/'+name+'/:'+primary_key_name do
53
+ # Must Exist
54
+ return 404 unless settings.send(service_name).exists_by_primary_key?(params[:code])
55
+
56
+ # Get The Data
57
+ begin
58
+ data = JSON.parse(request.body.read)
59
+ rescue Exception => e
60
+ return 400
61
+ end
62
+
63
+ # Valid Update?
64
+ return 400 unless settings.send(service_name).valid_update?(data)
65
+
66
+ # Do Update
67
+ record = settings.send(service_name).update_by_primary_key(params[:code],data)
68
+
69
+ # Other Error
70
+ return 500 if record.nil?
71
+
72
+ # Return new Region
73
+ JSON.fast_generate record
74
+ end
75
+
76
+ sinatra.delete '/'+name+'/:'+primary_key_name do
77
+ # Must Exist
78
+ return 404 unless settings.send(service_name).exists_by_primary_key?(params[:code])
79
+
80
+ # Do Delete
81
+ return 400 unless settings.send(service_name).delete_by_primary_key(params[:code])
82
+
83
+ 204
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,471 @@
1
+ require 'json'
2
+ require 'mysql2'
3
+
4
+ module CrudService
5
+ class GenericDal
6
+
7
+ attr_accessor :mysql, :memcache, :log, :table_name, :fields, :relations, :primary_key
8
+
9
+ def initialize(mysql, memcache, log)
10
+ @mysql = mysql
11
+ @memcache = memcache
12
+ @log = log
13
+ end
14
+
15
+ # Execute a Query, reading from cache if enabled.
16
+ def cached_query(query, tables)
17
+
18
+ unless @memcache.nil?
19
+
20
+ unless tables.include? @table_name
21
+ tables.push @table_name
22
+ tables.sort!
23
+ end
24
+
25
+ # Get Table versions
26
+ table_versions = ""
27
+
28
+ tables.each do |table|
29
+ tbversion = @memcache.get(table+"-version")
30
+ if tbversion.nil?
31
+ expire_table_cache([table])
32
+ tbversion = 1
33
+ end
34
+ table_versions += table+"-"+tbversion.to_s
35
+ end
36
+
37
+ # Get the Query Hash
38
+ querymd5 = "geoservice-"+Digest::MD5.hexdigest(query+":"+table_versions)
39
+
40
+ # Read Cache and return if hit
41
+ results = @memcache.get querymd5
42
+
43
+ unless results.nil?
44
+ return results
45
+ end
46
+
47
+ end
48
+
49
+ # Perform the Query
50
+ begin
51
+ queryresult = @mysql.query(query)
52
+ rescue Exception => e
53
+ @log.error("#{e}")
54
+ return []
55
+ end
56
+
57
+ # Collate Results
58
+ results = []
59
+ unless queryresult.nil? or queryresult.count == 0
60
+ queryresult.each do |h|
61
+ results.push h
62
+ end
63
+ end
64
+
65
+ unless @memcache.nil?
66
+ # Write to Cache
67
+ @memcache.set querymd5, results
68
+ end
69
+
70
+ # Return results
71
+ results
72
+ end
73
+
74
+ # Determine if all fields and includes in the query are available
75
+ def valid_query?(query)
76
+ return false if query.nil?
77
+ return true if query.keys.length == 0
78
+
79
+ query.each_key do |k|
80
+ return false if !@fields.has_key?(k) and k!='include' and k!='exclude'
81
+ end
82
+
83
+ get_includes(query).each do |k|
84
+ return false if !@fields.has_key?(k) and !@relations.has_key?(k)
85
+ end
86
+
87
+ get_excludes(query).each do |k|
88
+ return false if !@fields.has_key?(k)
89
+ end
90
+
91
+ true
92
+ end
93
+
94
+ # Build a simple where clause from the given query
95
+ def build_where(query)
96
+ where = ""
97
+ query.each_pair do |k, v|
98
+ if (k!='include' and k!='exclude')
99
+ where += "(`#{escape_str_field(k)}` #{build_equal_condition(v)}) AND "
100
+ end
101
+ end
102
+ where.chomp(' AND ')
103
+ end
104
+
105
+ def build_where_ns(query,ns)
106
+ where = ""
107
+ query.each_pair do |k, v|
108
+ if (k!='include' and k!='exclude')
109
+ where += "(`#{ns}`.`#{escape_str_field(k)}` #{build_equal_condition(v)}) AND "
110
+ end
111
+ end
112
+ where.chomp(' AND ')
113
+ end
114
+
115
+ # Build SQL INSERT fragment from data
116
+ def build_insert(data)
117
+ fields = ""
118
+ values = ""
119
+ data.each do |k,v|
120
+ fields += "`#{escape_str_field(k)}`, "
121
+ values += escape_value(v)+", "
122
+ end
123
+ "("+fields.chomp(', ')+") VALUES ("+values.chomp(', ')+")"
124
+ end
125
+
126
+ # Build SQL UPDATE fragment from data
127
+ def build_update(data)
128
+ sql = ""
129
+ data.each do |k,v|
130
+ sql += "`#{escape_str_field(k)}` = "+escape_value(v)+", "
131
+ end
132
+ sql.chomp(", ")
133
+ end
134
+
135
+ # Return an escaped condition string for the value v
136
+ def build_equal_condition(v)
137
+ if v.nil?
138
+ # Nulls (nil)
139
+ return "IS NULL"
140
+ elsif v.kind_of? Integer or v.kind_of? Float
141
+ # Integers / Floats
142
+ return "= "+v.to_s
143
+ else
144
+ # Everything Else
145
+ return "= '#{@mysql.escape(v.to_s)}'"
146
+ end
147
+ end
148
+
149
+ # Get fields
150
+ def build_select_fields(fields,ns)
151
+ select = ""
152
+ fields.each do |k|
153
+ select += "`#{ns}`." unless ns.nil?
154
+ select += "`#{k}`,"
155
+ end
156
+ select.chomp(',')
157
+ end
158
+
159
+ # Get fields
160
+ def build_fields(query)
161
+ build_select_fields(@fields.keys - get_excludes(query),nil)
162
+ end
163
+
164
+ # Get fields with a namespace
165
+ def build_fields_with_ns(query, ns)
166
+ build_select_fields(@fields.keys - get_excludes(query),ns)
167
+ end
168
+
169
+ # Return an escaped SQL string for the value v
170
+ def escape_value(v)
171
+ if v.nil?
172
+ # Nulls (nil)
173
+ return "NULL"
174
+ elsif v.kind_of? Integer or v.kind_of? Float
175
+ # Integers / Floats
176
+ return v.to_s
177
+ else
178
+ # Everything Else
179
+ return "'#{@mysql.escape(v.to_s)}'"
180
+ end
181
+ end
182
+
183
+ # Escape a field name
184
+ def escape_str_field(str)
185
+ str = str.to_s.sub(/\`/,'')
186
+ @mysql.escape(str)
187
+ end
188
+
189
+ # Get one record via a query
190
+ def get_one(query)
191
+ res = get_all_by_query(query)
192
+ return nil if res.length == 0
193
+ res[0]
194
+ end
195
+
196
+ # Get All records via a query
197
+ def get_all_by_query(query)
198
+ qry = "SELECT #{build_fields(query)} FROM `#{@table_name}`"
199
+ where = build_where(query)
200
+ qry += " WHERE #{where}" unless where.length == 0
201
+ cached_query(qry,[@table_name])
202
+ end
203
+
204
+ # Get all records for this entity and map ids to a hash
205
+ def get_all_by_query_as_hash(query)
206
+ map_to_hash_by_primary_key(get_all_by_query(query))
207
+ end
208
+
209
+ def map_in_included_relations!(result, query)
210
+ dat = get_relation_data_as_hash(query)
211
+ result.each do |res|
212
+ dat.each do |name, lookup|
213
+ res[name] = lookup[res[@relations[name][:this_key]]]
214
+ if @relations[name][:type] == :has_one
215
+ res[name] = res[name][0] unless res[name].nil?
216
+ else
217
+ res[name] = [] if res[name].nil?
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ # Get data for included relations for a query
224
+ def get_relation_data_as_hash(query)
225
+ return {} if @relations.nil?
226
+
227
+ includes = get_includes(query)
228
+
229
+ reldata = {}
230
+
231
+ @relations.each do |name, relation|
232
+ unless includes.find_index(name).nil?
233
+ sql = get_relation_query_sql(relation, query)
234
+ tables = get_relation_tables(relation)
235
+ data = cached_query(sql,tables)
236
+ reldata[name] = map_to_hash_of_arrays_by_key(data,'_table_key')
237
+ remove_key_from_hash_of_arrays!(reldata[name],'_table_key')
238
+ end
239
+ end
240
+ reldata
241
+ end
242
+
243
+ def remove_key_from_hash_of_arrays!(hash,key)
244
+ hash.each do |name,arr|
245
+ arr.each do |record|
246
+ record.delete(key)
247
+ end
248
+ end
249
+ hash
250
+ end
251
+
252
+ # Map a result array to a hash by primary key
253
+ def map_to_hash_by_primary_key(result)
254
+ hash = {}
255
+
256
+ result.each do |record|
257
+ hash[record[@primary_key]] = record
258
+ end
259
+
260
+ hash
261
+ end
262
+
263
+ # Map a result array to a hash of arrays by a specific key
264
+ def map_to_hash_of_arrays_by_key(result,key)
265
+ res = {}
266
+
267
+ result.each do |record|
268
+ res[record[key]] = [] unless res.has_key?(record[key])
269
+ res[record[key]].push record
270
+ end
271
+
272
+ res
273
+ end
274
+
275
+ # Add a field to each record from map using another field as a key
276
+ def add_field_from_map!(result, map, field_name, key_name)
277
+ out = []
278
+ result.each do |record|
279
+ record[field_name] = map[record[key_name]] if map.has_key?(record[key_name])
280
+ end
281
+ end
282
+
283
+ # Get includes
284
+ def get_includes(query)
285
+ return [] if query.nil? or !query.has_key?('include') or query['include'].nil?
286
+ query['include'].split(',')
287
+ end
288
+
289
+ # Get excludes
290
+ def get_excludes(query)
291
+ return [] if query.nil? or !query.has_key?('exclude') or query['exclude'].nil?
292
+ query['exclude'].split(',')
293
+ end
294
+
295
+ # Get sql to load relation
296
+ def get_relation_query_sql(relation, query)
297
+ case relation[:type]
298
+ when :has_one
299
+ return get_has_one_relation_query_sql(relation, query)
300
+ when :has_many
301
+ return get_has_many_relation_query_sql(relation, query)
302
+ when :has_many_through
303
+ return get_has_many_through_relation_query_sql(relation, query)
304
+ else
305
+ @log.error("Relation type #{relation[:type]} undefined!")
306
+ end
307
+ end
308
+
309
+ # Get the SQL query for a has_one relation
310
+ def get_has_one_relation_query_sql(relation, query)
311
+ fields = build_select_fields(relation[:table_fields].split(','),'a')
312
+
313
+ qry = "SELECT #{fields},`b`.`#{relation[:this_key]}` AS `_table_key` FROM `#{relation[:table]}` AS `a`, `#{@table_name}` AS `b` WHERE (`a`.`#{relation[:table_key]}` = `b`.`#{relation[:this_key]}`)"
314
+ where = build_where_ns(query,'b')
315
+ qry += " AND #{where}" unless where.length == 0
316
+ qry
317
+ end
318
+
319
+ # Get the SQL query for a has_many relation
320
+ def get_has_many_relation_query_sql(relation, query)
321
+ fields = build_select_fields(relation[:table_fields].split(','),'a')
322
+
323
+ qry = "SELECT #{fields},`b`.`#{relation[:this_key]}` AS `_table_key` FROM `#{relation[:table]}` AS `a`, `#{@table_name}` AS `b` WHERE (`a`.`#{relation[:table_key]}` = `b`.`#{relation[:this_key]}`)"
324
+ where = build_where_ns(query,'b')
325
+ qry += " AND #{where}" unless where.length == 0
326
+ qry
327
+ end
328
+
329
+ # Get the SQL query for a has_many_through relation
330
+ def get_has_many_through_relation_query_sql(relation,query)
331
+ fields = build_select_fields(relation[:table_fields].split(','),'a')
332
+
333
+ qry = "SELECT #{fields},`c`.`#{relation[:this_key]}` AS `_table_key` FROM `#{relation[:table]}` AS `a`, `#{relation[:link_table]}` AS `b`, `#{@table_name}` AS `c` WHERE (`a`.`#{relation[:table_key]}` = `b`.`#{relation[:link_field]}` AND `b`.`#{relation[:link_key]}` = `c`.`#{relation[:this_key]}`)"
334
+ where = build_where_ns(query,'c')
335
+ qry += " AND #{where}" unless where.length == 0
336
+ qry
337
+ end
338
+
339
+ # Get an array of table names involved in a relation query
340
+ def get_relation_tables(relation)
341
+ case relation[:type]
342
+ when :has_one
343
+ return [@table_name, relation[:table]].sort
344
+ when :has_many
345
+ return [@table_name, relation[:table]].sort
346
+ when :has_many_through
347
+ return [@table_name, relation[:table], relation[:link_table]].sort
348
+ else
349
+ throw "Unknown Relation type #{relation.type}"
350
+ end
351
+ end
352
+
353
+ # Expire a table cache by incrementing the table version
354
+ def expire_table_cache(table_names)
355
+ return if @memcache.nil?
356
+
357
+ table_names.each do |table_name|
358
+ key = table_name+"-version"
359
+ version = @memcache.get(key)
360
+ if version.nil?
361
+ @memcache.set(key,1,nil,{:raw=>true})
362
+ else
363
+ @memcache.incr(key, 1, nil)
364
+ end
365
+ end
366
+
367
+ true
368
+ end
369
+
370
+ # Return true if a key exists
371
+ def exists_by_primary_key?(primary_key)
372
+ qry = "SELECT COUNT(*) AS `c` FROM `#{@table_name}` WHERE "+build_where({@primary_key => primary_key})
373
+ res = cached_query(qry,[@table_name])
374
+ res[0]['c'] != 0
375
+ end
376
+
377
+ # Return true if an object is valid for create
378
+ def valid_insert?(data)
379
+ return false if data.nil?
380
+ return false if data.keys.length == 0
381
+
382
+ # Required fields
383
+ @fields.each do |k,s|
384
+ return false if s.has_key?(:required) and s[:required] == true and !data.has_key?(k)
385
+ end
386
+
387
+ # Only valid fields, length checking
388
+ data.each_key do |k|
389
+ return false if !@fields.has_key?(k)
390
+ return false if @fields[k].has_key?(:length) and
391
+ !data[k].nil? and
392
+ data[k].length > @fields[k][:length]
393
+ end
394
+
395
+ return true
396
+ end
397
+
398
+ # Return true if an object is valid for update
399
+ def valid_update?(data)
400
+ return false if data.nil?
401
+ return false if data.keys.length == 0
402
+
403
+ # Only valid fields, length checking
404
+ data.each_key do |k|
405
+ return false if !@fields.has_key?(k)
406
+ return false if @fields[k].has_key?(:length) and
407
+ !data[k].nil? and
408
+ data[k].length > @fields[k][:length]
409
+ end
410
+
411
+ return true
412
+ end
413
+
414
+ # Create a record from data
415
+ def insert(data)
416
+ query = "INSERT INTO `#{@table_name}` "+build_insert(data)
417
+
418
+ begin
419
+ queryresult = @mysql.query(query)
420
+ rescue Exception => e
421
+ @log.error("#{e}")
422
+ return false
423
+ end
424
+
425
+ expire_table_cache(get_all_related_tables)
426
+
427
+ get_one({@primary_key => data[@primary_key]})
428
+ end
429
+
430
+ # Update a record by its primary key from data
431
+ def update_by_primary_key(primary_key, data)
432
+ query = "UPDATE `#{@table_name}` SET "+build_update(data)+" WHERE "+build_where({@primary_key => primary_key})
433
+
434
+ begin
435
+ queryresult = @mysql.query(query)
436
+ rescue Exception => e
437
+ @log.error("#{e}")
438
+ return false
439
+ end
440
+
441
+ expire_table_cache(get_all_related_tables)
442
+
443
+ get_one({@primary_key => primary_key})
444
+ end
445
+
446
+ # Delete a record by its primary key from data
447
+ def delete_by_primary_key(primary_key)
448
+ query = "DELETE FROM `#{@table_name}` WHERE "+build_where({@primary_key => primary_key})
449
+
450
+ begin
451
+ queryresult = @mysql.query(query)
452
+ rescue Exception => e
453
+ @log.error("#{e}")
454
+ return false
455
+ end
456
+
457
+ expire_table_cache(get_all_related_tables)
458
+ true
459
+ end
460
+
461
+ # Return an array of all related tables plus this table
462
+ def get_all_related_tables
463
+ tables = [ @table_name ]
464
+ return tables if @relations.nil?
465
+ @relations.each do |n,r|
466
+ tables = tables | get_relation_tables(r)
467
+ end
468
+ tables.sort
469
+ end
470
+ end
471
+ end
@@ -0,0 +1,19 @@
1
+ module CrudService
2
+ class GenericLog
3
+ def debug(str)
4
+ puts "DEBUG: #{str}"
5
+ end
6
+
7
+ def info(str)
8
+ puts "INFO: #{str}"
9
+ end
10
+
11
+ def warn(str)
12
+ puts "WARN: #{str}"
13
+ end
14
+
15
+ def error(str)
16
+ puts "ERROR: #{str}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,57 @@
1
+ module CrudService
2
+ class GenericService
3
+
4
+ attr_accessor :dal, :log
5
+
6
+ def initialize(dal, log)
7
+ @dal = dal
8
+ @log = log
9
+ end
10
+
11
+ # CRUD
12
+
13
+ def insert(body)
14
+ @dal.insert(body)
15
+ end
16
+
17
+ def get_all_by_query(query)
18
+ res = @dal.get_all_by_query(query)
19
+ @dal.map_in_included_relations!(res,query)
20
+ res
21
+ end
22
+
23
+ def get_one_by_query(query)
24
+ res = get_all_by_query(query)
25
+ return nil if res.length == 0
26
+ res[0]
27
+ end
28
+
29
+ def update_by_primary_key(primary_key,body)
30
+ @dal.update_by_primary_key(primary_key,body)
31
+ end
32
+
33
+ def delete_by_primary_key(primary_key)
34
+ @dal.delete_by_primary_key(primary_key)
35
+ end
36
+
37
+ # Existence
38
+
39
+ def exists_by_primary_key?(primary_key)
40
+ @dal.exists_by_primary_key?(primary_key)
41
+ end
42
+
43
+ # Validation
44
+
45
+ def valid_insert?(body)
46
+ @dal.valid_insert?(body)
47
+ end
48
+
49
+ def valid_query?(query)
50
+ @dal.valid_query?(query)
51
+ end
52
+
53
+ def valid_update?(body)
54
+ @dal.valid_update?(body)
55
+ end
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crud-service
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Tom Cully
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A basic gem for automatic CRUD services using only Sinatra, MySQL and
14
+ Memcache
15
+ email: tomhughcully@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/generic_api.rb
21
+ - lib/generic_dal.rb
22
+ - lib/generic_log.rb
23
+ - lib/generic_service.rb
24
+ homepage: http://rubygems.org/gems/crud-service
25
+ licenses:
26
+ - Apache2
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.0.7
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: A Sinatra/MySQL/Memcache CRUD Service Library
48
+ test_files: []