crud-service 0.0.5 → 0.0.6

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