crud-service 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjFlNTQ2NGZlMjcxZDk3ZTMxMDRkZGRmMDJmMmMzNjc3MTU2MThkYQ==
4
+ MjM2MTBjNzg1ZmQwNjE4MzVlYWU5NzQzMzkzMGNjNDJlYTgxM2YwZA==
5
5
  data.tar.gz: !binary |-
6
- YWRjN2E1NjAxNjdjMjcwM2MwYTYyMThjMWM2NmM0NzgyMmVjZWMyZQ==
6
+ MjRlZWQ3MGM3OGY3MmIyMDQ2YzExMGJlZThiZGQ5NDM4MzI5M2ZlZA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MmFhMDZlYWMyMWJhYmM5OGUwNjc4NmI5OWUyM2NlOWE3MmZmZDU3YmZjNTAz
10
- NGI2OThmZTdhZmYyMGViNDRhZGVjYmE0NDgxMDFjNjE5ZTQ4ZmM4ZDBkNzVj
11
- MTBhZDMwNDkwMThkN2QxYTI4ZWRjNTZiMDIxNGI4OGQzMjZmM2M=
9
+ MGYzZWY1YzllYzRjNmI5OWMwZWNmYzBlOGMyZWRkOTIzNmY0MGQxOWVhZmVl
10
+ OTFlN2Y5ODZhZWYyZTViZDRkZGVlOGJkOWE1ZGI0MmY5ZjllZTkzYzYyZjUx
11
+ YzliNjUyMTY1NWEwNDFmYmNhMmE3NWM3YThlYWVjNGNhODk2YjE=
12
12
  data.tar.gz: !binary |-
13
- YTg4ZDBjYTI2ZmE1ZWUzOTE0ZGI2NDIxMjE2MGNjMjEzN2IwNTJjNDA0NzNk
14
- ZDkyMzQ2NGQ1ZGE4MTkxZTlhNjNjOTBlZjk4N2QzMDIyYjEzZWQwZmY0MjFj
15
- ODQ2NGY2NjQ3YmJkYzhjOTUzNzUyOGYyNTk1NjIxNDQ1ZmQ3MzE=
13
+ ZThlNWZiNDYwNDdmZTBmYzYyMmUxYTgwZDliMmUzZDg0ZTA5ZDA5ZDcwOWRl
14
+ ODU4ZDBkZGRjYzBhNDg1OGM0MmZiNmIyMmNkNTMwYjcwNWVkMzZkMzg0YWMw
15
+ ZmZiMTJlNTM3OWMyYWI5MWE5MTBmZGUyNjM0ZDY1MmZjNTY5ZGQ=
@@ -1,9 +1,7 @@
1
1
  module CrudService
2
-
3
2
  # This class provides a static method, crud_api, to configure a sinatra class with the
4
3
  # provided service, resource and primary key.
5
- class GenericApi
6
-
4
+ class Api
7
5
  def self.crud_api(sinatra, service_name, name, primary_key_name)
8
6
 
9
7
  sinatra.options '/'+name do
@@ -55,7 +53,7 @@ module CrudService
55
53
  sinatra.put '/'+name+'/:'+primary_key_name do
56
54
  # Must Exist
57
55
  return 404 unless settings.send(service_name).exists_by_primary_key?(params[:code])
58
-
56
+
59
57
  # Get The Data
60
58
  begin
61
59
  data = JSON.parse(request.body.read)
@@ -65,7 +63,7 @@ module CrudService
65
63
 
66
64
  # Valid Update?
67
65
  return 400 unless settings.send(service_name).valid_update?(data)
68
-
66
+
69
67
  # Do Update
70
68
  record = settings.send(service_name).update_by_primary_key(params[:code],data)
71
69
 
@@ -87,4 +85,4 @@ module CrudService
87
85
  end
88
86
  end
89
87
  end
90
- end
88
+ end
@@ -1,13 +1,4 @@
1
- # The crud-service gem provides a set of classes to quickly produce a basic CRUD API, from
2
- # a MySQL database with optional memcached caching.
3
- #
4
- # Author:: Tom Cully (mailto:tomhughcully@gmail.com)
5
- # Copyright:: Copyright (c) Tom Cully
6
- # License:: Apache 2
7
-
8
- base = File.expand_path('../', __FILE__)
9
-
10
- require "#{base}/generic_dal.rb"
11
- require "#{base}/generic_log.rb"
12
- require "#{base}/generic_service.rb"
13
- require "#{base}/generic_api.rb"
1
+ require "api"
2
+ require "dal"
3
+ require "logger"
4
+ require "service"
@@ -2,13 +2,11 @@ require 'json'
2
2
  require 'mysql2'
3
3
 
4
4
  module CrudService
5
-
6
5
  # This class creates an instance of a generic DAL (Data Access Layer) with cache
7
6
  # capability from the provided mysql client, logger and optionally memcache client.
8
7
  # Your should extend this class to provide configuration for your dal, please see
9
8
  # the README file at http://github.com/tomcully/crud-service
10
- class GenericDal
11
-
9
+ class Dal
12
10
  attr_accessor :mysql, :memcache, :log, :table_name, :fields, :relations, :primary_key
13
11
 
14
12
  # Create an instance.
@@ -20,7 +18,6 @@ module CrudService
20
18
 
21
19
  # Execute a Query, reading from cache if enabled.
22
20
  def cached_query(query, tables)
23
-
24
21
  unless @memcache.nil?
25
22
 
26
23
  unless tables.include? @table_name
@@ -81,7 +78,7 @@ module CrudService
81
78
  def valid_query?(query)
82
79
  return false if query.nil?
83
80
  return true if query.keys.length == 0
84
-
81
+
85
82
  query.each_key do |k|
86
83
  return false if !@fields.has_key?(k) and k!='include' and k!='exclude'
87
84
  end
@@ -384,7 +381,7 @@ module CrudService
384
381
  def valid_insert?(data)
385
382
  return false if data.nil?
386
383
  return false if data.keys.length == 0
387
-
384
+
388
385
  # Required fields
389
386
  @fields.each do |k,s|
390
387
  return false if s.has_key?(:required) and s[:required] == true and !data.has_key?(k)
@@ -452,7 +449,7 @@ module CrudService
452
449
  # Delete a record by its primary key from data
453
450
  def delete_by_primary_key(primary_key)
454
451
  query = "DELETE FROM `#{@table_name}` WHERE "+build_where({@primary_key => primary_key})
455
-
452
+
456
453
  begin
457
454
  queryresult = @mysql.query(query)
458
455
  rescue Exception => e
@@ -1,7 +1,6 @@
1
1
  module CrudService
2
-
3
2
  # This class provides a Generic Logger.
4
- class GenericLog
3
+ class Logger
5
4
  # Log a debug message
6
5
  def debug(str)
7
6
  puts "DEBUG: #{str}"
@@ -22,4 +21,4 @@ module CrudService
22
21
  puts "ERROR: #{str}"
23
22
  end
24
23
  end
25
- end
24
+ end
@@ -1,7 +1,5 @@
1
1
  module CrudService
2
-
3
- # This class provides a generic service instance for the provided DAL and logger.
4
- class GenericService
2
+ class Service
5
3
  attr_accessor :dal, :log
6
4
 
7
5
  # Instantiate a service with the specified DAL and logger.
@@ -59,4 +57,4 @@ module CrudService
59
57
  @dal.valid_update?(data)
60
58
  end
61
59
  end
62
- end
60
+ end
@@ -0,0 +1,12 @@
1
+ require "coveralls"
2
+ Coveralls.wear!
3
+ SimpleCov.coverage_dir('spec/coverage')
4
+
5
+ require "dotenv"
6
+ Dotenv.load
7
+
8
+ require "crud-service"
9
+
10
+ RSpec.configure do |c|
11
+ c.include Helpers
12
+ end
@@ -0,0 +1,30 @@
1
+ module Helpers
2
+ def mysql_mock
3
+ mysql = double("Mysql2")
4
+ mysql.stub(:escape) do |s|
5
+ Mysql2::Client.escape(s)
6
+ end
7
+ mysql
8
+ end
9
+
10
+ # Mock a Mysql Result object that returns each_hash
11
+ def mysql_result_mock(data)
12
+ result = double(result)
13
+ mock = result.stub(:each)
14
+
15
+ data.each do |hash|
16
+ mock = mock.and_yield(hash)
17
+ end
18
+
19
+ result.stub(:count).and_return(data.length)
20
+
21
+ result
22
+ end
23
+
24
+ # Make the supplied memcache_mock null (no hits, null write)
25
+ def memcache_null(memcache_mock)
26
+ @mock_memcache.stub(:get).and_return(nil)
27
+ @mock_memcache.stub(:set).and_return(nil)
28
+ @mock_memcache.stub(:incr).and_return(nil)
29
+ end
30
+ end
@@ -0,0 +1,1140 @@
1
+ require "helper"
2
+
3
+ describe CrudService::Dal do
4
+ before(:each) do
5
+ @mock_mysql = mysql_mock
6
+ @mock_memcache = double('Memcache')
7
+ @mock_log = double('Log')
8
+
9
+ @generic_dal = CrudService::Dal.new(@mock_mysql, @mock_memcache, @mock_log)
10
+ @generic_dal.table_name = "testtable"
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'should inject dependencies correctly' do
15
+ @generic_dal.mysql.should eq @mock_mysql
16
+ @generic_dal.memcache.should eq @mock_memcache
17
+ @generic_dal.log.should eq @mock_log
18
+ end
19
+ end
20
+
21
+ describe '#cached_query' do
22
+ it 'should attempt to query the cache before the database' do
23
+
24
+ testdata = [ { "field_one" => "one" } ]
25
+
26
+ mock_result = mysql_result_mock(testdata)
27
+
28
+ query = 'test invalid query'
29
+ query_hash = "geoservice-"+Digest::MD5.hexdigest(query+":testtable-1")
30
+
31
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(1)
32
+ @mock_memcache.should_receive(:get).ordered.with(query_hash).and_return(nil)
33
+ @mock_mysql.should_receive(:query).with(query).and_return(mock_result)
34
+ @mock_memcache.should_receive(:set).ordered.with(query_hash, testdata)
35
+
36
+ @generic_dal.cached_query(query,[]).should eq testdata
37
+ end
38
+
39
+ it 'should not attempt to query the database on a cache hit' do
40
+ testdata = [ { "field_one" => "one" } ]
41
+ query = 'test invalid query'
42
+ query_hash = "geoservice-"+Digest::MD5.hexdigest(query+":testtable-1")
43
+
44
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(1)
45
+ @mock_memcache.should_receive(:get).ordered.with(query_hash).and_return(testdata)
46
+ @mock_mysql.should_not_receive(:query)
47
+ @mock_memcache.should_not_receive(:set).ordered
48
+
49
+ @generic_dal.cached_query(query,[]).should eq testdata
50
+ end
51
+
52
+ it 'should handle zero record return' do
53
+ memcache_null(@mock_memcache)
54
+
55
+ query = 'test invalid query'
56
+
57
+ @mock_mysql.should_receive(:query).with(query).and_return(mysql_result_mock([]))
58
+
59
+ @generic_dal.cached_query(query,[]).should eq([])
60
+ end
61
+
62
+ it 'should write a new table version to cache when not found' do
63
+ testdata = [ { "field_one" => "one" } ]
64
+
65
+ mock_result = mysql_result_mock(testdata)
66
+
67
+ query = 'test invalid query'
68
+ query_hash = "geoservice-"+Digest::MD5.hexdigest(query+":testtable-1")
69
+
70
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(nil)
71
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(nil)
72
+ @mock_memcache.should_receive(:set).ordered.with("testtable-version",1,nil,{:raw=>true})
73
+ @mock_memcache.should_receive(:get).ordered.with(query_hash).and_return(nil)
74
+ @mock_mysql.should_receive(:query).ordered.with(query).and_return(mock_result)
75
+ @mock_memcache.should_receive(:set).ordered.with(query_hash, testdata)
76
+
77
+ @generic_dal.cached_query(query,[]).should eq testdata
78
+ end
79
+
80
+ it 'should miss the cache when a table version has changed' do
81
+ testdata = [ { "field_one" => "one" } ]
82
+
83
+ mock_result = mysql_result_mock(testdata)
84
+
85
+ query = 'test invalid query'
86
+ query_hash = "geoservice-"+Digest::MD5.hexdigest(query+":testtable-1")
87
+
88
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(1)
89
+ @mock_memcache.should_receive(:get).ordered.with(query_hash).and_return(nil)
90
+ @mock_mysql.should_receive(:query).with(query).and_return(mock_result)
91
+ @mock_memcache.should_receive(:set).ordered.with(query_hash, testdata)
92
+
93
+ @generic_dal.cached_query(query,[]).should eq testdata
94
+
95
+ query_hash = "geoservice-"+Digest::MD5.hexdigest(query+":testtable-2")
96
+
97
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(2)
98
+ @mock_memcache.should_receive(:get).ordered.with(query_hash).and_return(nil)
99
+ @mock_mysql.should_receive(:query).with(query).and_return(mock_result)
100
+ @mock_memcache.should_receive(:set).ordered.with(query_hash, testdata)
101
+
102
+ @generic_dal.cached_query(query,[]).should eq testdata
103
+ end
104
+
105
+ end
106
+
107
+ describe '#build_where' do
108
+ it 'should return an empty string when called with no query' do
109
+ query = { }
110
+ @generic_dal.build_where(query).should eq ""
111
+ end
112
+
113
+ it 'should return a valid where clause when called with a single field query string value' do
114
+ query = { "one" => "two" }
115
+ @generic_dal.build_where(query).should eq "(`one` = 'two')"
116
+ end
117
+
118
+ it 'should return a valid where clause when called with a single field query integer value' do
119
+ query = { "one" => 2 }
120
+ @generic_dal.build_where(query).should eq "(`one` = 2)"
121
+ end
122
+
123
+ it 'should return a valid where clause when called with a single field query float value' do
124
+ query = { "one" => 2.123 }
125
+ @generic_dal.build_where(query).should eq "(`one` = 2.123)"
126
+ end
127
+
128
+ it 'should return a valid where clause when called with a multiple field query' do
129
+ query = { "one" => "two", "three" => "four" }
130
+ @generic_dal.build_where(query).should eq "(`one` = 'two') AND (`three` = 'four')"
131
+ end
132
+
133
+ it 'should return a valid where clause when called with a query with a nil value' do
134
+ query = { "one" => "two", "three" => nil}
135
+ @generic_dal.build_where(query).should eq "(`one` = 'two') AND (`three` IS NULL)"
136
+ end
137
+
138
+ it 'should escape field names' do
139
+ query = { "on`=1; DROP TABLE countries" => "two" }
140
+ @generic_dal.build_where(query).should eq "(`on=1; DROP TABLE countries` = 'two')"
141
+ end
142
+
143
+ it 'should escape field values when string based' do
144
+ query = { "one" => "two'; DROP TABLE countries;" }
145
+ @generic_dal.build_where(query).should eq "(`one` = 'two\\'; DROP TABLE countries;')"
146
+ end
147
+
148
+ it 'should not build include or exclude into queries' do
149
+ query = { "one" => 2, "include" => "subdivisions", "exclude" => "countries", "two"=>3 }
150
+ @generic_dal.build_where(query).should eq "(`one` = 2) AND (`two` = 3)"
151
+ end
152
+ end
153
+
154
+ describe '#build_where_ns_ns' do
155
+ it 'should return an empty string when called with no query' do
156
+ query = { }
157
+ @generic_dal.build_where_ns(query,'a').should eq ""
158
+ end
159
+
160
+ it 'should return a valid where clause when called with a single field query string value' do
161
+ query = { "one" => "two" }
162
+ @generic_dal.build_where_ns(query,'b').should eq "(`b`.`one` = 'two')"
163
+ end
164
+
165
+ it 'should return a valid where clause when called with a single field query integer value' do
166
+ query = { "one" => 2 }
167
+ @generic_dal.build_where_ns(query,'c').should eq "(`c`.`one` = 2)"
168
+ end
169
+
170
+ it 'should return a valid where clause when called with a single field query float value' do
171
+ query = { "one" => 2.123 }
172
+ @generic_dal.build_where_ns(query,'d').should eq "(`d`.`one` = 2.123)"
173
+ end
174
+
175
+ it 'should return a valid where clause when called with a multiple field query' do
176
+ query = { "one" => "two", "three" => "four" }
177
+ @generic_dal.build_where_ns(query,'e').should eq "(`e`.`one` = 'two') AND (`e`.`three` = 'four')"
178
+ end
179
+
180
+ it 'should return a valid where clause when called with a query with a nil value' do
181
+ query = { "one" => "two", "three" => nil}
182
+ @generic_dal.build_where_ns(query,'f').should eq "(`f`.`one` = 'two') AND (`f`.`three` IS NULL)"
183
+ end
184
+
185
+ it 'should escape field names' do
186
+ query = { "on`=1; DROP TABLE countries" => "two" }
187
+ @generic_dal.build_where_ns(query,'g').should eq "(`g`.`on=1; DROP TABLE countries` = 'two')"
188
+ end
189
+
190
+ it 'should escape field values when string based' do
191
+ query = { "one" => "two'; DROP TABLE countries;" }
192
+ @generic_dal.build_where_ns(query,'h').should eq "(`h`.`one` = 'two\\'; DROP TABLE countries;')"
193
+ end
194
+
195
+ it 'should not build include or exclude into queries' do
196
+ query = { "one" => 2, "include" => "subdivisions", "exclude" => "countries", "two"=>3 }
197
+ @generic_dal.build_where_ns(query,'i').should eq "(`i`.`one` = 2) AND (`i`.`two` = 3)"
198
+ end
199
+ end
200
+
201
+ describe '#build_fields' do
202
+ it 'should return an empty string with no fields' do
203
+ @generic_dal.build_select_fields([],nil).should eq ""
204
+ end
205
+
206
+ it 'should return fields correctly' do
207
+ @generic_dal.build_select_fields(['one','two'],nil).should eq "`one`,`two`"
208
+ end
209
+
210
+ it 'should return namespaced fields correctly' do
211
+ @generic_dal.build_select_fields(['one','two'],'a').should eq "`a`.`one`,`a`.`two`"
212
+ end
213
+ end
214
+
215
+ describe '#build_fields' do
216
+ before(:each) do
217
+ @generic_dal.fields = {
218
+ "test1" => { :type=>:string },
219
+ "test2" => { :type=>:string },
220
+ "testX" => { :type=>:string },
221
+ }
222
+ end
223
+
224
+ it 'should return all fields with nil excludes' do
225
+ @generic_dal.build_fields({}).should eq "`test1`,`test2`,`testX`"
226
+ end
227
+
228
+ it 'should return all fields with empty excludes' do
229
+ @generic_dal.build_fields({"exclude"=>nil}).should eq "`test1`,`test2`,`testX`"
230
+ end
231
+
232
+ it 'should exclude a single field' do
233
+ @generic_dal.build_fields({"exclude"=>'test1'}).should eq "`test2`,`testX`"
234
+ end
235
+
236
+ it 'should exclude multiple fields' do
237
+ @generic_dal.build_fields({"exclude"=>'test1,testX'}).should eq "`test2`"
238
+ end
239
+ end
240
+
241
+ describe '#build_fields_with_ns' do
242
+ before(:each) do
243
+ @generic_dal.fields = {
244
+ "test1" => { :type=>:string },
245
+ "test2" => { :type=>:string },
246
+ "testX" => { :type=>:string },
247
+ }
248
+ end
249
+
250
+ it 'should return all fields with nil excludes' do
251
+ @generic_dal.build_fields_with_ns({},'a').should eq "`a`.`test1`,`a`.`test2`,`a`.`testX`"
252
+ end
253
+
254
+ it 'should return all fields with empty excludes' do
255
+ @generic_dal.build_fields_with_ns({"exclude"=>nil},'b').should eq "`b`.`test1`,`b`.`test2`,`b`.`testX`"
256
+ end
257
+
258
+ it 'should exclude a single field' do
259
+ @generic_dal.build_fields_with_ns({"exclude"=>'test1'},'c').should eq "`c`.`test2`,`c`.`testX`"
260
+ end
261
+
262
+ it 'should exclude multiple fields' do
263
+ @generic_dal.build_fields_with_ns({"exclude"=>'test1,testX'},'d').should eq "`d`.`test2`"
264
+ end
265
+ end
266
+
267
+ describe '#get_includes' do
268
+ before(:each) do
269
+ @generic_dal.fields = ["test1", "test2", "testX"]
270
+ end
271
+
272
+ it 'should return an empty array with a nil query' do
273
+ @generic_dal.get_includes(nil).should eq []
274
+ end
275
+
276
+ it 'should return an empty array with no fields or includes' do
277
+ query = { }
278
+ @generic_dal.get_includes(query).should eq []
279
+ end
280
+
281
+ it 'should return an empty array with fields and no includes' do
282
+ query = { "field2" => "xxas"}
283
+ @generic_dal.get_includes(query).should eq []
284
+ end
285
+
286
+ it 'should return a single include' do
287
+ query = { "include"=>"test1" }
288
+ @generic_dal.get_includes(query).should eq ['test1']
289
+ end
290
+
291
+ it 'should return multiple includes' do
292
+ query = { "include"=>"test1,test2"}
293
+ @generic_dal.get_includes(query).should eq ['test1','test2']
294
+ end
295
+ end
296
+
297
+ describe '#get_excludes' do
298
+ before(:each) do
299
+ @generic_dal.fields = {
300
+ "test1" => { :type=>:string },
301
+ "test2" => { :type=>:string },
302
+ "testX" => { :type=>:string },
303
+ }
304
+ end
305
+
306
+ it 'should return an empty array with a nil query' do
307
+ @generic_dal.get_excludes(nil).should eq []
308
+ end
309
+
310
+ it 'should return an empty array with no fields or excludes' do
311
+ query = { }
312
+ @generic_dal.get_excludes(query).should eq []
313
+ end
314
+
315
+ it 'should return an empty array with fields and no excludes' do
316
+ query = { "field2" => "xxas"}
317
+ @generic_dal.get_excludes(query).should eq []
318
+ end
319
+
320
+ it 'should return a single exclude' do
321
+ query = { "exclude"=>"test1", "field2" => "xxas"}
322
+ @generic_dal.get_excludes(query).should eq ['test1']
323
+ end
324
+
325
+ it 'should return multiple excludes' do
326
+ query = { "exclude"=>"test1,test2"}
327
+ @generic_dal.get_excludes(query).should eq ['test1','test2']
328
+ end
329
+ end
330
+
331
+ describe '#build_equal_condition' do
332
+ it 'should return IS NULL for a nil' do
333
+ @generic_dal.build_equal_condition(nil).should eq 'IS NULL'
334
+ end
335
+
336
+ it 'should return correct response for an integer' do
337
+ @generic_dal.build_equal_condition(1).should eq '= 1'
338
+ end
339
+
340
+ it 'should return correct response for a float' do
341
+ @generic_dal.build_equal_condition(1.123).should eq '= 1.123'
342
+ end
343
+
344
+ it 'should return correct response for a string' do
345
+ @generic_dal.build_equal_condition('ABC').should eq "= 'ABC'"
346
+ end
347
+
348
+ it 'should return correct escaped response for a string' do
349
+ @generic_dal.build_equal_condition("AB'; DROP TABLE test_table --").should eq "= 'AB\\'; DROP TABLE test_table --'"
350
+ end
351
+ end
352
+
353
+ describe '#valid_query?' do
354
+ before(:each) do
355
+ @generic_dal.fields = {
356
+ "one" => { :type=>:string },
357
+ "two" => { :type=>:string },
358
+ "three" => { :type=>:string },
359
+ }
360
+ @generic_dal.relations = {
361
+ "four" => { :type=>:string },
362
+ "five" => { :type=>:string },
363
+ "six" => { :type=>:string },
364
+ }
365
+ end
366
+
367
+ it 'should return true with valid fields' do
368
+ @generic_dal.valid_query?({"one"=>1}).should be true
369
+ end
370
+
371
+ it 'should return false with invalid fields' do
372
+ @generic_dal.valid_query?({"five"=>1}).should be false
373
+ end
374
+
375
+ it 'should return true with valid relations' do
376
+ @generic_dal.valid_query?({"include"=>'four,five'}).should be true
377
+ end
378
+
379
+ it 'should return false with invalid relations' do
380
+ @generic_dal.valid_query?({"include"=>'ten'}).should be false
381
+ end
382
+
383
+ it 'should return false with nil' do
384
+ @generic_dal.valid_query?(nil).should be false
385
+ end
386
+
387
+ it 'should return true with no fields' do
388
+ @generic_dal.valid_query?({}).should be true
389
+ end
390
+
391
+ it 'should return true regardless of include' do
392
+ @generic_dal.valid_query?({"one"=>1,"include"=>"two"}).should be true
393
+ end
394
+
395
+ it 'should return true regardless of exclude' do
396
+ @generic_dal.valid_query?({"one"=>1,"exclude"=>"one"}).should be true
397
+ end
398
+
399
+ it 'should return false as cannot exclude a relation' do
400
+ @generic_dal.valid_query?({"one"=>1,"exclude"=>"five"}).should be false
401
+ end
402
+ end
403
+
404
+ describe '#escape_str_field' do
405
+ it 'should escape single quotes' do
406
+ @generic_dal.escape_str_field("ABC'BC").should eq "ABC\\'BC"
407
+ end
408
+
409
+ it 'should remove backtics' do
410
+ @generic_dal.escape_str_field("ABC`BC").should eq "ABCBC"
411
+ end
412
+
413
+ it 'should resolve symbols as well as strings' do
414
+ @generic_dal.escape_str_field(:testing).should eq "testing"
415
+ end
416
+ end
417
+
418
+ describe '#get_all_by_query' do
419
+ it 'should call cached_query with the correct query for one field' do
420
+ memcache_null(@mock_memcache)
421
+ @generic_dal.fields = {
422
+ "one" => { :type=>:string },
423
+ "two" => { :type=>:string },
424
+ }
425
+ @generic_dal.table_name = 'test_table'
426
+
427
+ @mock_mysql.should_receive(:query).with("SELECT `one`,`two` FROM `test_table` WHERE (`field` = 'test2')")
428
+
429
+ @generic_dal.get_all_by_query({ :field => 'test2' })
430
+ end
431
+
432
+ it 'should call cached_query with the correct query for multiple fields' do
433
+ memcache_null(@mock_memcache)
434
+ @generic_dal.fields = {
435
+ "one" => { :type=>:string },
436
+ "two" => { :type=>:string },
437
+ }
438
+ @generic_dal.table_name = 'test_table'
439
+
440
+ @mock_mysql.should_receive(:query).with("SELECT `one`,`two` FROM `test_table` WHERE (`field` = 'test2') AND (`twofield` = 2) AND (`nullfield` IS NULL)")
441
+
442
+ @generic_dal.get_all_by_query({ :field => 'test2', "twofield" =>2, "nullfield" => nil })
443
+ end
444
+ end
445
+
446
+ describe '#get_one' do
447
+ before(:each) do
448
+ memcache_null(@mock_memcache)
449
+
450
+ @generic_dal.fields = {
451
+ "one" => { :type=>:string },
452
+ "two" => { :type=>:string },
453
+ }
454
+
455
+ @generic_dal.table_name = 'test_table'
456
+
457
+ @mock_result = mysql_result_mock([
458
+ { "field_one" => "one" },
459
+ { "field_one" => "two" }
460
+ ])
461
+ end
462
+
463
+ it 'should call cached_query with the correct query for one field and return a single object' do
464
+ @mock_mysql.should_receive(:query)
465
+ .with("SELECT `one`,`two` FROM `test_table` WHERE (`field` = 'test2')")
466
+ .and_return(@mock_result)
467
+
468
+ @generic_dal.get_one({ :field => 'test2' }).should eq({ "field_one" => "one" })
469
+ end
470
+
471
+ it 'should call cached_query with the correct query for one field and return a single object' do
472
+ @mock_mysql.should_receive(:query)
473
+ .with("SELECT `one`,`two` FROM `test_table` WHERE (`field` = 'test2') AND (`field_two` = 'test3')")
474
+ .and_return(@mock_result)
475
+
476
+ @generic_dal.get_one({ :field => 'test2', :field_two => 'test3' }).should eq({ "field_one" => "one" })
477
+ end
478
+ end
479
+
480
+ describe '#map_to_hash_by_primary_key' do
481
+ before(:each) do
482
+ @generic_dal.primary_key = 'id'
483
+ end
484
+
485
+ it 'should return an empty hash when given an empty array' do
486
+ test = []
487
+
488
+ @generic_dal.map_to_hash_by_primary_key(test).should eq({})
489
+ end
490
+
491
+ it 'should correctly map an array' do
492
+ test = [
493
+ { "id" => 1, "field_one" => "one" },
494
+ { "id" => 2.5, "field_one" => "two point five" },
495
+ { "id" => "3", "field_one" => "three" },
496
+ { "id" => nil, "field_one" => "four" }
497
+ ]
498
+
499
+ @generic_dal.map_to_hash_by_primary_key(test).should eq({
500
+ 1 => { "id" => 1, "field_one" => "one" },
501
+ 2.5 => { "id" => 2.5, "field_one" => "two point five" },
502
+ "3" => { "id" => "3", "field_one" => "three" },
503
+ nil => { "id" => nil, "field_one" => "four" }
504
+ })
505
+ end
506
+ end
507
+
508
+ describe '#remove_key_from_hash_of_arrays!' do
509
+
510
+ it 'should remove a key from each hash in each array in each hash value' do
511
+
512
+ test = {
513
+ 'one' => [ ],
514
+ 2 => [ {"x" => 'a', "y" => 'b', 'z' => 'c' } ],
515
+ nil => [ {"x" => 'd', "y" => 'e', 'z' => 'f' }, {"x" => 'g', "y" => 'h', 'z' => 'i' } ],
516
+ }
517
+
518
+ @generic_dal.remove_key_from_hash_of_arrays!(test,'z')
519
+
520
+ test.should eq({
521
+ 'one' => [ ],
522
+ 2 => [ {"x" => 'a', "y" => 'b'} ],
523
+ nil => [ {"x" => 'd', "y" => 'e' }, {"x" => 'g', "y" => 'h' } ],
524
+ })
525
+
526
+ end
527
+ end
528
+
529
+ describe '#map_to_hash_of_arrays_by_key' do
530
+ it 'should return an empty hash when given an empty array' do
531
+ test = []
532
+
533
+ @generic_dal.map_to_hash_of_arrays_by_key(test,'field_one').should eq({})
534
+ end
535
+
536
+ it 'should correctly map an array' do
537
+ test = [
538
+ { "id" => 1, "field_one" => 1 },
539
+ { "id" => 2.5, "field_one" => "two point five" },
540
+ { "id" => "3", "field_one" => "three" },
541
+ { "id" => nil, "field_one" => 4.5 },
542
+ { "id" => nil, "field_one" => 1 },
543
+ { "id" => 90, "field_one" => "two point five" },
544
+ { "id" => nil, "field_one" => "four" },
545
+ { "id" => "16", "field_one" => "three" },
546
+ { "id" => 2.1, "field_one" => 4.5 },
547
+ { "id" => 328, "field_one" => "one" },
548
+ { "id" => nil, "field_one" => nil },
549
+ { "id" => 123, "field_one" => nil },
550
+ ]
551
+
552
+ @generic_dal.map_to_hash_of_arrays_by_key(test,'field_one').should eq({
553
+ nil => [
554
+ { "id" => nil, "field_one" => nil },
555
+ { "id" => 123, "field_one" => nil },
556
+ ],
557
+ 1 => [
558
+ { "id" => 1, "field_one" => 1 },
559
+ { "id" => nil, "field_one" => 1 },
560
+ ],
561
+ "two point five" => [
562
+ { "id" => 2.5, "field_one" => "two point five" },
563
+ { "id" => 90, "field_one" => "two point five" },
564
+ ],
565
+ "three" => [
566
+ { "id" => "3", "field_one" => "three" },
567
+ { "id" => "16", "field_one" => "three" },
568
+ ],
569
+ "four" => [
570
+ { "id" => nil, "field_one" => "four" },
571
+ ],
572
+ 4.5 => [
573
+ { "id" => nil, "field_one" => 4.5 },
574
+ { "id" => 2.1, "field_one" => 4.5 },
575
+ ],
576
+ "one" => [
577
+ { "id" => 328, "field_one" => "one" },
578
+ ]
579
+ })
580
+ end
581
+ end
582
+
583
+ describe '#add_field_from_map!' do
584
+ it 'should map correctly' do
585
+ records = [
586
+ {"id"=>1, "fk_code"=>"EU", "name"=>"Test1" },
587
+ {"id"=>2, "fk_code"=>"EU", "name"=>"Test2" },
588
+ {"id"=>3, "fk_code"=>"AU", "name"=>"Test3" },
589
+ {"id"=>4, "fk_code"=>"GB", "name"=>"Test4" },
590
+ {"id"=>5, "fk_code"=>"US", "name"=>"Test5" },
591
+ {"id"=>6, "fk_code"=>nil, "name"=>"Test5" },
592
+ ]
593
+
594
+ map = {
595
+ 'EU' => 1,
596
+ 'AU' => { "name"=>"one" },
597
+ 'US' => nil,
598
+ 'GB' => "test!"
599
+ }
600
+
601
+ @generic_dal.add_field_from_map!(records, map, 'fk_field', 'fk_code')
602
+
603
+ records.should eq [
604
+ {"id"=>1, "fk_code"=>"EU", "name"=>"Test1", "fk_field"=>1 },
605
+ {"id"=>2, "fk_code"=>"EU", "name"=>"Test2", "fk_field"=>1 },
606
+ {"id"=>3, "fk_code"=>"AU", "name"=>"Test3", "fk_field"=>{ "name"=>"one" } },
607
+ {"id"=>4, "fk_code"=>"GB", "name"=>"Test4", "fk_field"=>"test!" },
608
+ {"id"=>5, "fk_code"=>"US", "name"=>"Test5", "fk_field"=>nil },
609
+ {"id"=>6, "fk_code"=>nil, "name"=>"Test5" },
610
+ ]
611
+
612
+ end
613
+ end
614
+
615
+ describe '#get_relation_query_sql' do
616
+ it 'should return the correct sql for a has_one relation with no query' do
617
+
618
+ @generic_dal.table_name = "currencies"
619
+
620
+ rel = {
621
+ :type => :has_one,
622
+ :table => 'countries',
623
+ :table_key => 'default_currency_code',
624
+ :this_key => 'code',
625
+ :table_fields => 'code_alpha_2,name',
626
+ }
627
+
628
+ @generic_dal.get_relation_query_sql(rel,{}).should eq(
629
+ "SELECT `a`.`code_alpha_2`,`a`.`name`,`b`.`code` AS `_table_key` FROM `countries` AS `a`, `currencies` AS `b` WHERE (`a`.`default_currency_code` = `b`.`code`)"
630
+ )
631
+
632
+ end
633
+
634
+ it 'should return the correct sql for a has_one relation with a query' do
635
+
636
+ @generic_dal.table_name = "currencies"
637
+
638
+ rel = {
639
+ :type => :has_one,
640
+ :table => 'countries',
641
+ :table_key => 'default_currency_code',
642
+ :this_key => 'code',
643
+ :table_fields => 'code_alpha_2,name',
644
+ }
645
+
646
+ @generic_dal.get_relation_query_sql(rel,{'testfield'=>1}).should eq(
647
+ "SELECT `a`.`code_alpha_2`,`a`.`name`,`b`.`code` AS `_table_key` FROM `countries` AS `a`, `currencies` AS `b` WHERE (`a`.`default_currency_code` = `b`.`code`) AND (`b`.`testfield` = 1)"
648
+ )
649
+
650
+ end
651
+
652
+ it 'should return the correct sql for a has_many relation' do
653
+
654
+ @generic_dal.table_name = "houses"
655
+
656
+ rel = {
657
+ :type => :has_many,
658
+ :table => 'cats',
659
+ :table_key => 'house_id',
660
+ :this_key => 'id',
661
+ :table_fields => 'cat_id,name',
662
+ }
663
+
664
+ @generic_dal.get_relation_query_sql(rel,{}).should eq(
665
+ "SELECT `a`.`cat_id`,`a`.`name`,`b`.`id` AS `_table_key` FROM `cats` AS `a`, `houses` AS `b` WHERE (`a`.`house_id` = `b`.`id`)"
666
+ )
667
+
668
+ end
669
+
670
+ it 'should return the correct sql for a has_many relation with a query' do
671
+
672
+ @generic_dal.table_name = "houses"
673
+
674
+ rel = {
675
+ :type => :has_many,
676
+ :table => 'cats',
677
+ :table_key => 'house_id',
678
+ :this_key => 'id',
679
+ :table_fields => 'cat_id,name',
680
+ }
681
+
682
+ @generic_dal.get_relation_query_sql(rel,{"colour"=>"ginger"}).should eq(
683
+ "SELECT `a`.`cat_id`,`a`.`name`,`b`.`id` AS `_table_key` FROM `cats` AS `a`, `houses` AS `b` WHERE (`a`.`house_id` = `b`.`id`) AND (`b`.`colour` = 'ginger')"
684
+ )
685
+
686
+ end
687
+
688
+ it 'should return the correct sql for a has_many_through relation' do
689
+
690
+ @generic_dal.table_name = "countries"
691
+
692
+ rel = {
693
+ :type => :has_many_through,
694
+ :table => 'regions',
695
+ :link_table => 'region_countries',
696
+ :link_key => 'country_code_alpha_2',
697
+ :link_field => 'region_code',
698
+ :table_key => 'code',
699
+ :this_key => 'code_alpha_2',
700
+ :table_fields => 'code,name',
701
+ }
702
+
703
+ @generic_dal.get_relation_query_sql(rel,{}).should eq(
704
+ "SELECT `a`.`code`,`a`.`name`,`c`.`code_alpha_2` AS `_table_key` FROM `regions` AS `a`, `region_countries` AS `b`, `countries` AS `c` WHERE (`a`.`code` = `b`.`region_code` AND `b`.`country_code_alpha_2` = `c`.`code_alpha_2`)"
705
+ )
706
+
707
+ end
708
+
709
+ it 'should return the correct sql for a has_many_through relation with a query' do
710
+
711
+ @generic_dal.table_name = "countries"
712
+
713
+ rel = {
714
+ :type => :has_many_through,
715
+ :table => 'regions',
716
+ :link_table => 'region_countries',
717
+ :link_key => 'country_code_alpha_2',
718
+ :link_field => 'region_code',
719
+ :table_key => 'code',
720
+ :this_key => 'code_alpha_2',
721
+ :table_fields => 'code,name',
722
+ }
723
+
724
+ @generic_dal.get_relation_query_sql(rel,{"default_currency_code"=>"EUR"}).should eq(
725
+ "SELECT `a`.`code`,`a`.`name`,`c`.`code_alpha_2` AS `_table_key` FROM `regions` AS `a`, `region_countries` AS `b`, `countries` AS `c` WHERE (`a`.`code` = `b`.`region_code` AND `b`.`country_code_alpha_2` = `c`.`code_alpha_2`) AND (`c`.`default_currency_code` = 'EUR')"
726
+ )
727
+
728
+ end
729
+ end
730
+
731
+ describe "#get_relation_tables" do
732
+ it 'should return the correct tables for a has_one relation' do
733
+ @generic_dal.table_name = "currencies"
734
+
735
+ rel = {
736
+ :type => :has_one,
737
+ :table => 'countries',
738
+ :table_key => 'default_currency_code',
739
+ :this_key => 'code',
740
+ :table_fields => 'code_alpha_2,name',
741
+ }
742
+
743
+ @generic_dal.get_relation_tables(rel).should eq(["countries", "currencies"])
744
+ end
745
+
746
+ it 'should return the correct tables for a has_many relation' do
747
+ @generic_dal.table_name = "houses"
748
+
749
+ rel = {
750
+ :type => :has_many,
751
+ :table => 'cats',
752
+ :table_key => 'house_id',
753
+ :this_key => 'id',
754
+ :table_fields => 'cat_id,name',
755
+ }
756
+
757
+ @generic_dal.get_relation_tables(rel).should eq(["cats", "houses"])
758
+ end
759
+
760
+ it 'should return the correct tables for a has_many_through relation' do
761
+ @generic_dal.table_name = "countries"
762
+
763
+ rel = {
764
+ :type => :has_many_through,
765
+ :table => 'regions',
766
+ :link_table => 'region_countries',
767
+ :link_key => 'country_code_alpha_2',
768
+ :link_field => 'region_code',
769
+ :table_key => 'code',
770
+ :this_key => 'code_alpha_2',
771
+ :table_fields => 'code,name',
772
+ }
773
+
774
+ @generic_dal.get_relation_tables(rel).should eq(["countries","region_countries","regions"])
775
+ end
776
+ end
777
+
778
+ describe '#expire_table_cache' do
779
+ it 'should set a table version when it doesnt exist' do
780
+
781
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(nil)
782
+ @mock_memcache.should_receive(:set).ordered.with("testtable-version",1,nil,{:raw=>true}).and_return(nil)
783
+
784
+ @generic_dal.expire_table_cache(['testtable'])
785
+ end
786
+
787
+ it 'should increment a table version when it exists' do
788
+
789
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(1)
790
+ @mock_memcache.should_receive(:incr).ordered.with("testtable-version",1,nil).and_return(nil)
791
+
792
+ @generic_dal.expire_table_cache(['testtable'])
793
+ end
794
+
795
+ it 'should expire multiple tables' do
796
+
797
+ @mock_memcache.should_receive(:get).ordered.with("testtable-version").and_return(1)
798
+ @mock_memcache.should_receive(:incr).ordered.with("testtable-version",1,nil).and_return(nil)
799
+ @mock_memcache.should_receive(:get).ordered.with("tabletwo-version").and_return(1)
800
+ @mock_memcache.should_receive(:incr).ordered.with("tabletwo-version",1,nil).and_return(nil)
801
+
802
+ @generic_dal.expire_table_cache(['testtable','tabletwo'])
803
+ end
804
+ end
805
+
806
+ describe '#exists_by_primary_key?' do
807
+ before do
808
+ memcache_null(@mock_memcache)
809
+
810
+ @generic_dal.table_name = 'pktesttable'
811
+ @generic_dal.primary_key = 'id'
812
+
813
+ @mock_result = mysql_result_mock([ { "c" => 1 } ])
814
+ end
815
+
816
+ it 'should call cached_query with correct sql with a numeric primary key' do
817
+ @mock_mysql.should_receive(:query).with("SELECT COUNT(*) AS `c` FROM `pktesttable` WHERE (`id` = 2002)").and_return(@mock_result)
818
+
819
+ @generic_dal.exists_by_primary_key?(2002).should eq(true)
820
+ end
821
+
822
+ it 'should call cached_query with correct sql with a string primary key' do
823
+ @mock_mysql.should_receive(:query).with("SELECT COUNT(*) AS `c` FROM `pktesttable` WHERE (`id` = 'test')").and_return(@mock_result)
824
+
825
+ @generic_dal.exists_by_primary_key?('test').should eq(true)
826
+ end
827
+
828
+ it 'should return true when count is not 0' do
829
+ @mock_result = mysql_result_mock([ { "c" => 1 } ])
830
+
831
+ @mock_mysql.should_receive(:query).with("SELECT COUNT(*) AS `c` FROM `pktesttable` WHERE (`id` = 'test')").and_return(@mock_result)
832
+
833
+ @generic_dal.exists_by_primary_key?('test').should eq(true)
834
+ end
835
+
836
+ it 'should return false when count is 0' do
837
+ @mock_result = mysql_result_mock([ { "c" => 0 } ])
838
+
839
+ @mock_mysql.should_receive(:query).with("SELECT COUNT(*) AS `c` FROM `pktesttable` WHERE (`id` = 'test')").and_return(@mock_result)
840
+
841
+ @generic_dal.exists_by_primary_key?('test').should eq(false)
842
+ end
843
+ end
844
+
845
+ describe '#valid_insert?' do
846
+ it 'should return false if object nil' do
847
+ @generic_dal.valid_insert?(nil).should eq(false)
848
+ end
849
+
850
+ it 'should return false if object empty' do
851
+ @generic_dal.valid_insert?({}).should eq(false)
852
+ end
853
+
854
+ it 'should return true if all fields exist' do
855
+ @generic_dal.fields = {
856
+ "one" => { :type=>:string },
857
+ "two" => { :type=>:string },
858
+ }
859
+
860
+ @generic_dal.valid_insert?({ "one"=>"1", "two"=>"2" }).should eq(true)
861
+ end
862
+
863
+ it 'should return false if fields do not exist' do
864
+ @generic_dal.fields = {
865
+ "one" => { :type=>:string },
866
+ "two" => { :type=>:string },
867
+ }
868
+
869
+ @generic_dal.valid_insert?({ "five"=>"1", "two"=>"2" }).should eq(false)
870
+ end
871
+
872
+ it 'should return true if data is within the max length' do
873
+ @generic_dal.fields = {
874
+ "one" => { :type=>:string },
875
+ "two" => { :type=>:string, :length=>4 },
876
+ }
877
+
878
+ @generic_dal.valid_insert?({ "one"=>"1", "two"=>"2" }).should eq(true)
879
+ end
880
+
881
+ it 'should return false if data is greater than the max length' do
882
+ @generic_dal.fields = {
883
+ "one" => { :type=>:string },
884
+ "two" => { :type=>:string, :length=>4 },
885
+ }
886
+
887
+ @generic_dal.valid_insert?({ "one"=>"1", "two"=>"22332" }).should eq(false)
888
+ end
889
+
890
+ it 'should return false if required key is missing' do
891
+ @generic_dal.fields = {
892
+ "one" => { :type=>:string },
893
+ "two" => { :type=>:string, :required=>true },
894
+ }
895
+
896
+ @generic_dal.valid_insert?({ "one"=>"1" }).should eq(false)
897
+ end
898
+
899
+ it 'should return true if required keys are ok' do
900
+ @generic_dal.fields = {
901
+ "one" => { :type=>:string, :required=>true },
902
+ "two" => { :type=>:string, :required=>true },
903
+ }
904
+
905
+ @generic_dal.valid_insert?({ "one"=>"1","two"=>"2" }).should eq(true)
906
+ end
907
+ end
908
+
909
+ describe '#valid_update?' do
910
+ it 'should return false if object nil' do
911
+ @generic_dal.valid_update?(nil).should eq(false)
912
+ end
913
+
914
+ it 'should return false if object empty' do
915
+ @generic_dal.valid_update?({}).should eq(false)
916
+ end
917
+
918
+ it 'should return true if all fields exist' do
919
+ @generic_dal.fields = {
920
+ "one" => { :type=>:string },
921
+ "two" => { :type=>:string },
922
+ }
923
+
924
+ @generic_dal.valid_update?({ "one"=>"1", "two"=>"2" }).should eq(true)
925
+ end
926
+
927
+ it 'should return false if fields do not exist' do
928
+ @generic_dal.fields = {
929
+ "one" => { :type=>:string },
930
+ "two" => { :type=>:string },
931
+ }
932
+
933
+ @generic_dal.valid_update?({ "five"=>"1", "two"=>"2" }).should eq(false)
934
+ end
935
+
936
+ it 'should return false if data is greater than the max length' do
937
+ @generic_dal.fields = {
938
+ "one" => { :type=>:string },
939
+ "two" => { :type=>:string, :length=>4 },
940
+ }
941
+
942
+ @generic_dal.valid_update?({ "one"=>"1", "two"=>"22332" }).should eq(false)
943
+ end
944
+ end
945
+
946
+ describe "#escape_value" do
947
+ it 'should return NULL for nil' do
948
+ @generic_dal.escape_value(nil).should eq('NULL')
949
+ end
950
+
951
+ it 'should return integer for int/float' do
952
+ @generic_dal.escape_value(1).should eq('1')
953
+ @generic_dal.escape_value(1.45).should eq('1.45')
954
+ end
955
+
956
+ it 'should return a quoted string for string' do
957
+ @generic_dal.escape_value('test').should eq("'test'")
958
+ end
959
+
960
+ it 'should escape sql values properly' do
961
+ @generic_dal.escape_value("test '; DROP TABLE test; --").should eq("'test \\'; DROP TABLE test; --'")
962
+ end
963
+ end
964
+
965
+ describe "#build_insert" do
966
+ it 'should return correct SQL fragment for basic fields' do
967
+ data = {
968
+ "one" => 1,
969
+ "two" => "2",
970
+ "three" => nil,
971
+ }
972
+
973
+ @generic_dal.build_insert(data).should eq("(`one`, `two`, `three`) VALUES (1, '2', NULL)")
974
+ end
975
+
976
+ it 'should escape field names and data' do
977
+ data = {
978
+ "one`; DROP TABLE test; -- " => 1,
979
+ "two" => "two",
980
+ "three" => "'; DROP TABLE test; --'",
981
+ }
982
+
983
+ @generic_dal.build_insert(data).should eq("(`one; DROP TABLE test; -- `, `two`, `three`) VALUES (1, 'two', '\\'; DROP TABLE test; --\\'')")
984
+ end
985
+ end
986
+
987
+ describe "#build_update" do
988
+ it 'should return correct SQL fragment for basic fields' do
989
+ data = {
990
+ "one" => 1,
991
+ "two" => "two",
992
+ "three" => nil,
993
+ }
994
+
995
+ @generic_dal.build_update(data).should eq("`one` = 1, `two` = 'two', `three` = NULL")
996
+ end
997
+
998
+ it 'should escape field names and data' do
999
+ data = {
1000
+ "one`; DROP TABLE test; -- " => 1,
1001
+ "two" => "2",
1002
+ "three" => "'; DROP TABLE test; --'",
1003
+ }
1004
+
1005
+ @generic_dal.build_update(data).should eq("`one; DROP TABLE test; -- ` = 1, `two` = '2', `three` = '\\'; DROP TABLE test; --\\''")
1006
+ end
1007
+ end
1008
+
1009
+ describe "#get_all_related_tables" do
1010
+ it 'should return the table name for nil relations' do
1011
+ @generic_dal.table_name = 'test1'
1012
+
1013
+ @generic_dal.relations = nil
1014
+
1015
+ @generic_dal.get_all_related_tables.should eq(["test1"])
1016
+ end
1017
+
1018
+ it 'should return the table name for empty relations' do
1019
+ @generic_dal.table_name = 'test1'
1020
+
1021
+ @generic_dal.relations = {}
1022
+
1023
+ @generic_dal.get_all_related_tables.should eq(["test1"])
1024
+ end
1025
+
1026
+ it 'should return the table name for a single relations' do
1027
+ @generic_dal.table_name = 'test1'
1028
+
1029
+ @generic_dal.relations = {
1030
+ 'countries' => {
1031
+ :type => :has_one,
1032
+ :table => 'countries',
1033
+ :table_key => 'default_currency_code',
1034
+ :this_key => 'code',
1035
+ :table_fields => 'code_alpha_2,name',
1036
+ },
1037
+ }
1038
+
1039
+ @generic_dal.get_all_related_tables.should eq(["countries","test1"])
1040
+ end
1041
+
1042
+
1043
+ it 'should return the correct table names for multiple relations with dedupe' do
1044
+ @generic_dal.table_name = 'test1'
1045
+
1046
+ @generic_dal.relations = {
1047
+ 'countries' => {
1048
+ :type => :has_one,
1049
+ :table => 'countries',
1050
+ :table_key => 'default_currency_code',
1051
+ :this_key => 'code',
1052
+ :table_fields => 'code_alpha_2,name',
1053
+ },
1054
+ 'countries2' => {
1055
+ :type => :has_many,
1056
+ :table => 'countries',
1057
+ :table_key => 'default_currency_code',
1058
+ :this_key => 'code',
1059
+ :table_fields => 'code_alpha_2,name',
1060
+ },
1061
+ 'regions' => {
1062
+ :type => :has_many_through,
1063
+ :table => 'regions',
1064
+ :link_table => 'region_countries',
1065
+ :link_key => 'country_code_alpha_2',
1066
+ :link_field => 'region_code',
1067
+ :table_key => 'code',
1068
+ :this_key => 'code_alpha_2',
1069
+ :table_fields => 'code,name',
1070
+ }
1071
+ }
1072
+
1073
+ @generic_dal.get_all_related_tables.should eq(["countries", "region_countries", "regions", "test1"])
1074
+ end
1075
+ end
1076
+
1077
+ describe '#insert' do
1078
+ it 'should call the correct sql and expire the correct cache' do
1079
+ testdata = { "field_one" => "one" }
1080
+
1081
+ @generic_dal.table_name = "test_table"
1082
+ @generic_dal.fields = {
1083
+ "field_one" => { :type => :integer }
1084
+ }
1085
+
1086
+ query = "INSERT INTO `test_table` (`field_one`) VALUES ('one')"
1087
+
1088
+ @mock_mysql.should_receive(:query).ordered.with(query)
1089
+
1090
+ @mock_memcache.should_receive(:get).ordered.with('test_table-version').and_return(1)
1091
+ @mock_memcache.should_receive(:incr).ordered.with('test_table-version',1,nil)
1092
+
1093
+ @mock_memcache.should_receive(:get).ordered.with('test_table-version').and_return(1)
1094
+ @mock_memcache.should_receive(:get).ordered.and_return([{ "field_one" => "one","id"=>1 }])
1095
+
1096
+ @generic_dal.insert(testdata)
1097
+ end
1098
+ end
1099
+
1100
+ describe '#update_by_primary_key' do
1101
+ it 'should call the correct sql and expire the correct cache' do
1102
+ testdata = { "field_one" => "two" }
1103
+
1104
+ @generic_dal.table_name = "test_table"
1105
+ @generic_dal.primary_key = "code"
1106
+ @generic_dal.fields = {
1107
+ "field_one" => { :type => :integer }
1108
+ }
1109
+
1110
+ query = "UPDATE `test_table` SET `field_one` = 'two' WHERE (`code` = 2)"
1111
+
1112
+ @mock_mysql.should_receive(:query).ordered.with(query)
1113
+
1114
+ @mock_memcache.should_receive(:get).ordered.with('test_table-version').and_return(1)
1115
+ @mock_memcache.should_receive(:incr).ordered.with('test_table-version',1,nil)
1116
+
1117
+ @mock_memcache.should_receive(:get).ordered.with('test_table-version').and_return(1)
1118
+ @mock_memcache.should_receive(:get).ordered.and_return([{ "field_one" => "two","id"=>2}])
1119
+
1120
+ @generic_dal.update_by_primary_key(2, testdata)
1121
+ end
1122
+ end
1123
+
1124
+ describe '#delete_by_primary_key' do
1125
+ it 'should call the correct sql and expire the correct cache' do
1126
+
1127
+ @generic_dal.table_name = "test_table"
1128
+ @generic_dal.primary_key = "code"
1129
+
1130
+ query = "DELETE FROM `test_table` WHERE (`code` = 'three')"
1131
+
1132
+ @mock_mysql.should_receive(:query).ordered.with(query)
1133
+ @mock_memcache.should_receive(:get).ordered.with('test_table-version').and_return(1)
1134
+ @mock_memcache.should_receive(:incr).ordered.with('test_table-version',1,nil)
1135
+
1136
+ @generic_dal.delete_by_primary_key('three')
1137
+ end
1138
+ end
1139
+
1140
+ end