rquerypad 0.1.11
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.
- data/CHANGELOG +48 -0
- data/MIT-LICENSE +20 -0
- data/README +66 -0
- data/Rakefile +32 -0
- data/lib/rquerypad.rb +318 -0
- data/tasks/rquerypad_tasks.rake +4 -0
- data/test/db.rquerypad +0 -0
- data/test/fixtures/replies.yml +25 -0
- data/test/fixtures/threads.yml +17 -0
- data/test/fixtures/users.yml +7 -0
- data/test/migrate/001_create_users.rb +13 -0
- data/test/migrate/002_create_threads.rb +15 -0
- data/test/migrate/003_create_replies.rb +17 -0
- data/test/model.rb +17 -0
- data/test/rquerypad_test.rb +25 -0
- data/test/test_helper.rb +43 -0
- metadata +74 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
Changes in version 0.1.11 (2008-04-29)
|
2
|
+
-------------------------------------
|
3
|
+
fix bugs about invalid cache for options
|
4
|
+
Changes in version 0.1.10 (2008-03-26)
|
5
|
+
-------------------------------------
|
6
|
+
make alias_method name with 4rqp(for rquerypad) suffix to avoid confliction with the 3rd plugins
|
7
|
+
|
8
|
+
Changes in version 0.1.9 (2008-03-26)
|
9
|
+
-------------------------------------
|
10
|
+
use deep copy to fix bug about options in cache is changed by later operation
|
11
|
+
|
12
|
+
Changes in version 0.1.8 (2008-03-26)
|
13
|
+
-------------------------------------
|
14
|
+
use deep copy to fix bug about options in cache is changed by later operation
|
15
|
+
|
16
|
+
Changes in version 0.1.7 (2008-03-26)
|
17
|
+
-------------------------------------
|
18
|
+
fix option value nil problem due to some 3rd plugins
|
19
|
+
|
20
|
+
Changes in version 0.1.6 (2008-03-25)
|
21
|
+
-------------------------------------
|
22
|
+
fix bug about cache key not bind to class
|
23
|
+
|
24
|
+
Changes in version 0.1.5 (2008-03-21)
|
25
|
+
-------------------------------------
|
26
|
+
fix bug when field is a id with single table which has belongs_to association, such as "parent.id"
|
27
|
+
|
28
|
+
Changes in version 0.1.4 (2008-03-20)
|
29
|
+
-------------------------------------
|
30
|
+
support calculations call, such as count/avg...
|
31
|
+
|
32
|
+
Changes in version 0.1.3 (2008-03-20)
|
33
|
+
-------------------------------------
|
34
|
+
fix about rails 1.2.6 support
|
35
|
+
|
36
|
+
Changes in version 0.1.2 (2008-03-19)
|
37
|
+
-------------------------------------
|
38
|
+
fix inner join bug
|
39
|
+
|
40
|
+
Changes in version 0.1.1 (2008-03-19)
|
41
|
+
-------------------------------------
|
42
|
+
put initialized code in rquerypad's init.rb, instead of project configuration
|
43
|
+
support to rails 1.2.6
|
44
|
+
|
45
|
+
|
46
|
+
Changes in version 0.1.0 (2008-03-18)
|
47
|
+
-------------------------------------
|
48
|
+
fix bug about table_name.field as parameter
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Rquerypad, release 0.1.11 (Apr. 2008)
|
2
|
+
=========
|
3
|
+
Simplify query options with association automation and improve inner join for activerecord of rails
|
4
|
+
|
5
|
+
Feature
|
6
|
+
=======
|
7
|
+
1. single name string decribe associations
|
8
|
+
2. support to mix inner join and outer join with any order
|
9
|
+
3. auto remove duplicated joins from other association in different depth
|
10
|
+
4. support :conditions, :order, :group
|
11
|
+
5. auto merge to original :include, :joins
|
12
|
+
|
13
|
+
Install
|
14
|
+
=======
|
15
|
+
ruby script/plugin install http://rquerypad.rubyforge.org/svn/trunk/rquerypad
|
16
|
+
or
|
17
|
+
ruby script/plugin install http://rquerypad.googlecode.com/svn/trunk/rquerypad
|
18
|
+
|
19
|
+
Example
|
20
|
+
=======
|
21
|
+
suppose the asscociations of User <-> Thread <-> Reply is 1:N:N
|
22
|
+
|
23
|
+
@users = User.find(:all, :group => ["threads.created_at", "name"])
|
24
|
+
generate:
|
25
|
+
[:all, {:group=>"threads.created_at, users.name", :include=>[:threads]}]
|
26
|
+
|
27
|
+
@users = User.find(:all, :conditions => ["threads_.replies.title = ?", "rquerypad"])
|
28
|
+
generate:
|
29
|
+
[:all, {:inner_joins=>["threads"], :conditions=>["replies.title = ?", "rquerypad"], :include=>[{:threads=>:replies}]}]
|
30
|
+
#note: the :inner_joints is processed by rquerypad before sending sql to database
|
31
|
+
|
32
|
+
@users = User.find(:all, :conditions => ["threads.replies.title = ? and threads.id = ?", "rquerypad", 1])
|
33
|
+
generate:
|
34
|
+
[:all, {:conditions=>["replies.title = ? and threads.id = ?", "rquerypad", 1], :include=>[{:threads=>:replies}]}]
|
35
|
+
#note: single "threads" was removed from includes
|
36
|
+
|
37
|
+
Setup
|
38
|
+
=======
|
39
|
+
#to set debug model, in rails initialized script
|
40
|
+
$RQUERYPAD_DEBUG = true
|
41
|
+
|
42
|
+
Test
|
43
|
+
=======
|
44
|
+
Note: current migrate script works only in rails 2.0
|
45
|
+
|
46
|
+
1.Prepare
|
47
|
+
|
48
|
+
the test depends on sqlite3 database, the following code should be add into your database.yml and place rquerypad.rb(copy from test.rb) in config/environment
|
49
|
+
|
50
|
+
rquerypad:
|
51
|
+
adapter: sqlite3
|
52
|
+
database: vendor/plugins/rquerypad/test/db.rquerypad
|
53
|
+
timeout: 5000
|
54
|
+
|
55
|
+
2.database migrate
|
56
|
+
|
57
|
+
execute the following script
|
58
|
+
|
59
|
+
rake migrate
|
60
|
+
|
61
|
+
3.start test
|
62
|
+
|
63
|
+
execute the following script
|
64
|
+
rake
|
65
|
+
|
66
|
+
Copyright (c) 2008 Leon Li, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
PKG_NAME = "rquerypad"
|
4
|
+
PKG_VERSION = "0.1.11"
|
5
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
6
|
+
PKG_FILES = FileList[
|
7
|
+
'[A-Za-z]*',
|
8
|
+
'lib/**/*',
|
9
|
+
'test/**/*',
|
10
|
+
'tasks/**/*',
|
11
|
+
]
|
12
|
+
spec = Gem::Specification.new do |s|
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
s.summary = "Simplify query options with association automation and improve inner join for activerecord of rails"
|
15
|
+
s.name = PKG_NAME
|
16
|
+
s.version = PKG_VERSION
|
17
|
+
s.requirements << 'none'
|
18
|
+
s.require_path = 'lib'
|
19
|
+
s.rubyforge_project = 'rquerypad'
|
20
|
+
#s.executables = ['gem-sample']
|
21
|
+
#s.default_executable = 'gem-sample'
|
22
|
+
s.authors = ["Leon Li"]
|
23
|
+
s.email = "scorpio_leon@hotmail.com"
|
24
|
+
s.files = PKG_FILES
|
25
|
+
s.description = <<-EOF
|
26
|
+
Simplify query options with association automation and improve inner join for activerecord of rails
|
27
|
+
EOF
|
28
|
+
end
|
29
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
30
|
+
pkg.need_zip = true
|
31
|
+
pkg.need_tar = true
|
32
|
+
end
|
data/lib/rquerypad.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
=begin Rquerypad
|
2
|
+
author: Leon Li(scorpio_leon@hotmail.com)
|
3
|
+
=end
|
4
|
+
require 'md5'
|
5
|
+
class Rquerypad
|
6
|
+
#todo scope support
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def prepare_with_debug
|
10
|
+
prepare(true)
|
11
|
+
end
|
12
|
+
def prepare(debug = $RQUERYPAD_DEBUG)
|
13
|
+
return nil unless @prepared.nil?
|
14
|
+
@prepared = true
|
15
|
+
ActiveRecord::Base.class_eval %{
|
16
|
+
require "rquerypad"
|
17
|
+
|
18
|
+
class << self
|
19
|
+
@@rquerypad_cache = {}
|
20
|
+
alias_method(:old_find_4rqp, :find) unless method_defined?(:old_find_4rqp)
|
21
|
+
alias_method(:old_cfswia_4rqp, :construct_finder_sql_with_included_associations) unless method_defined?(:old_cfswia_4rqp)
|
22
|
+
VALID_FIND_OPTIONS << :inner_joins
|
23
|
+
def find(*args)
|
24
|
+
options = #{if [].respond_to?(:extract_options!) then "args.extract_options!" else "extract_options_from_args!(args)" end}
|
25
|
+
if !options.empty? && (options.include?(:conditions) || options.include?(:group) || options.include?(:order))
|
26
|
+
# if (!options.empty? && (!options[:conditions].nil? || !options[:group].nil? || !options[:order].nil?))
|
27
|
+
#{"p 'original options:', options" if debug}
|
28
|
+
cache_key = Rquerypad.options_key(options)
|
29
|
+
#p "cache_key: #\{cache_key\}"
|
30
|
+
#p "rquerypad_cache[cache_key]: #\{rquerypad_cache[cache_key]\}"
|
31
|
+
if rquerypad_cache[cache_key].nil?
|
32
|
+
#{"p 'process for:', options" if debug}
|
33
|
+
Rquerypad.new(self).improve_options!(options)
|
34
|
+
rquerypad_cache[cache_key] = Marshal.dump(options)
|
35
|
+
#p "rquerypad_cache[cache_key]: #\{rquerypad_cache[cache_key]\}"
|
36
|
+
else
|
37
|
+
options = Marshal.load(rquerypad_cache[cache_key])
|
38
|
+
end
|
39
|
+
#{"p 'new options:', options" if debug}
|
40
|
+
end
|
41
|
+
args += [options] unless options.empty?
|
42
|
+
old_find_4rqp *args
|
43
|
+
end
|
44
|
+
def construct_finder_sql_with_included_associations(options, join_dependency)
|
45
|
+
sql = old_cfswia_4rqp(options, join_dependency)
|
46
|
+
#{"p('original sql', sql) unless options[:inner_joins].nil?" if debug}
|
47
|
+
sql = Rquerypad.process_inner_join(sql, options[:inner_joins]) unless options[:inner_joins].nil?
|
48
|
+
#{"p('new sql with inner join', sql) unless options[:inner_joins].nil?" if debug}
|
49
|
+
sql
|
50
|
+
end
|
51
|
+
def rquerypad_cache
|
52
|
+
@@rquerypad_cache
|
53
|
+
end
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
ActiveRecord::Calculations.class_eval %{
|
58
|
+
require "rquerypad"
|
59
|
+
module ClassMethods
|
60
|
+
@@rquerypad_cache = {}
|
61
|
+
alias_method(:old_construct_calculation_sql_4rqp, :construct_calculation_sql) unless method_defined?(:old_construct_calculation_sql_4rqp)
|
62
|
+
CALCULATIONS_OPTIONS << :inner_joins
|
63
|
+
def construct_calculation_sql(operation, column_name, options)
|
64
|
+
if !options.empty? && (options.include?(:conditions) || options.include?(:group) || options.include?(:order))
|
65
|
+
#{"p 'original options:', options" if debug}
|
66
|
+
cache_key = Rquerypad.options_key(options)
|
67
|
+
if rquerypad_cache[cache_key].nil?
|
68
|
+
#{"p 'process for:', options" if debug}
|
69
|
+
Rquerypad.new(self).improve_options!(options)
|
70
|
+
rquerypad_cache[cache_key] = Marshal.load(Marshal.dump(options))
|
71
|
+
else
|
72
|
+
options = rquerypad_cache[cache_key]
|
73
|
+
end
|
74
|
+
#{"p 'new options:', options" if debug}
|
75
|
+
end
|
76
|
+
sql = old_construct_calculation_sql_4rqp(operation, column_name, options)
|
77
|
+
#{"p('original sql', sql) unless options[:inner_joins].nil?" if debug}
|
78
|
+
sql = Rquerypad.process_inner_join(sql, options[:inner_joins]) unless options[:inner_joins].nil?
|
79
|
+
#{"p('new sql with inner join', sql) unless options[:inner_joins].nil?" if debug}
|
80
|
+
sql
|
81
|
+
end
|
82
|
+
def rquerypad_cache
|
83
|
+
@@rquerypad_cache
|
84
|
+
end
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
Hash.class_eval do
|
89
|
+
def single_lt?(other)
|
90
|
+
return false if self == other
|
91
|
+
return true if self.size == 0
|
92
|
+
return false unless other.is_a?(Hash)
|
93
|
+
return false if other.nil? || other.size < self.size
|
94
|
+
return false if self.size != other.size || self.size > 1
|
95
|
+
a = self.to_a[0]
|
96
|
+
o = other.to_a[0]
|
97
|
+
return false if a[0] != o[0]
|
98
|
+
return true unless a[1].is_a?(Hash)
|
99
|
+
a[1].single_lt?(o[1])
|
100
|
+
end
|
101
|
+
def single_gt?(other)
|
102
|
+
return false if self == other
|
103
|
+
return !(single_lt?(other))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_inner_join(sql, inner_joins)
|
109
|
+
inner_joins.each {|i| sql.gsub!(/LEFT OUTER JOIN [`]?#{i}[`]?/, "INNER JOIN #{i}")} unless inner_joins.empty?
|
110
|
+
sql
|
111
|
+
end
|
112
|
+
|
113
|
+
def options_key(options)
|
114
|
+
key = options.to_a.to_s
|
115
|
+
key = MD5.hexdigest(key) if key.length > 100
|
116
|
+
key
|
117
|
+
end
|
118
|
+
end
|
119
|
+
def initialize(obj)
|
120
|
+
@owner = obj
|
121
|
+
@class_name = obj.to_s.scan(/(\w*::)*([^\(]*)/)[0][1]
|
122
|
+
@table_name = @class_name.pluralize.underscore
|
123
|
+
@tnwithdot = @table_name + "."
|
124
|
+
@new_include = []
|
125
|
+
@old_include = nil
|
126
|
+
@inner_joins = []
|
127
|
+
end
|
128
|
+
|
129
|
+
def improve_options!(option_hash)
|
130
|
+
|
131
|
+
option_hash.each do |key, value|
|
132
|
+
next if value.blank?
|
133
|
+
if key.to_s == "conditions"
|
134
|
+
option_hash[key] = improve_conditions(value)
|
135
|
+
elsif (key.to_s == "order" || key.to_s == "group" || key.to_s == "group_field")
|
136
|
+
option_hash[key] = improve_ordergroup(value).join(", ")
|
137
|
+
elsif (key.to_s == "include")
|
138
|
+
@old_include = value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return nil if @new_include.empty?
|
142
|
+
#generate new include
|
143
|
+
|
144
|
+
remove_dup_includes
|
145
|
+
unless @new_include.nil?
|
146
|
+
option_hash[:include] = [] if @old_include.nil?
|
147
|
+
@new_include += @old_include unless @old_include.nil?
|
148
|
+
option_hash.each do |key, value|
|
149
|
+
if key.to_s == "include"
|
150
|
+
option_hash[key] = @new_include
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
option_hash[:inner_joins] = @inner_joins unless @inner_joins.empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
def remove_dup_includes
|
158
|
+
final_includes = []
|
159
|
+
@new_include.each do |ni|
|
160
|
+
next if final_includes.include?(ni)
|
161
|
+
if final_includes.size > 0
|
162
|
+
final_includes.each_index do |i|
|
163
|
+
if ni.is_a?(Hash)
|
164
|
+
if final_includes[i].is_a?(Hash)
|
165
|
+
if final_includes[i].single_lt?(ni)
|
166
|
+
final_includes[i] = ni
|
167
|
+
elsif final_includes[i].single_gt?(ni)
|
168
|
+
else
|
169
|
+
final_includes << ni
|
170
|
+
end
|
171
|
+
elsif ni.entries[0][0].to_s == final_includes[i].to_s
|
172
|
+
final_includes[i] = ni
|
173
|
+
else
|
174
|
+
final_includes << ni
|
175
|
+
end
|
176
|
+
else
|
177
|
+
if final_includes[i].is_a?(Hash)
|
178
|
+
final_includes << ni if final_includes[i].entries[0][0].to_s != ni.to_s
|
179
|
+
else
|
180
|
+
final_includes << ni if final_includes[i].to_s != ni.to_s
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
else
|
185
|
+
final_includes << ni
|
186
|
+
end
|
187
|
+
end
|
188
|
+
@new_include = final_includes
|
189
|
+
end
|
190
|
+
|
191
|
+
def improve_conditions(options)
|
192
|
+
if options.is_a?(Array)
|
193
|
+
str = options[0]
|
194
|
+
temp = str.scan(/'.*?'/)
|
195
|
+
#replace string in quote to avoid unecessary processing
|
196
|
+
str.gsub!(/'.*?'/, "'[??]'") unless temp.empty?
|
197
|
+
str.gsub!(/(([\w\(\)]+\.)+\w+)[ !><=]/) do |n|
|
198
|
+
#cut last char and abstract association for include
|
199
|
+
abstract_association(n[0..-2]) + n[-1, 1]
|
200
|
+
end
|
201
|
+
#add @table_name on single field
|
202
|
+
str.gsub!(/[\.]?\w+[\.]?/) {|x| x[-1, 1] == "." || ["and", "or", "is", "null", "not"].include?(x.downcase) ? x : @tnwithdot + x}
|
203
|
+
str.gsub!(/\.#{@table_name}\./, ".")
|
204
|
+
#recover string in quote
|
205
|
+
unless temp.empty?
|
206
|
+
i = -1
|
207
|
+
str.gsub!(/\'\[\?\?\]\'/) do
|
208
|
+
i += 1
|
209
|
+
temp[i]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
options[0] = str
|
213
|
+
end
|
214
|
+
if options.is_a?(Hash)
|
215
|
+
#work around frozen issue
|
216
|
+
new_options = {}
|
217
|
+
until options.empty?
|
218
|
+
key, value = options.shift
|
219
|
+
key = key.to_s.dup
|
220
|
+
new_options[process_single!(key)] = value
|
221
|
+
end
|
222
|
+
options.merge!(new_options)
|
223
|
+
end
|
224
|
+
options
|
225
|
+
end
|
226
|
+
|
227
|
+
def process_single!(key)
|
228
|
+
if key.include?(".")
|
229
|
+
if /^(.+)[ ]/ =~ key
|
230
|
+
key.sub!("#$1", abstract_association("#$1"))
|
231
|
+
else
|
232
|
+
key.sub!(/^.+$/, abstract_association(key))
|
233
|
+
end
|
234
|
+
else
|
235
|
+
key.sub!(/^.+$/, @tnwithdot + key)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def improve_ordergroup(fields)
|
240
|
+
fields = [fields] if fields.is_a?(String)
|
241
|
+
fields.each {|field| process_single!(field)}
|
242
|
+
fields
|
243
|
+
end
|
244
|
+
|
245
|
+
def abstract_association(str)
|
246
|
+
result = nil
|
247
|
+
owner = @owner
|
248
|
+
names = str.split(".")
|
249
|
+
return str if names.size == 2 && names[0] == @table_name
|
250
|
+
#seperate assocations/tables and field
|
251
|
+
tables, field = names[0..-2], names[-1]
|
252
|
+
owners = []
|
253
|
+
#get relevant owner for each table
|
254
|
+
tables.each_index do |i|
|
255
|
+
if i == 0
|
256
|
+
owners[i] = owner
|
257
|
+
else
|
258
|
+
tname = cut_end_underscore(tables[i-1])
|
259
|
+
r = owners[i-1].reflections[tname.to_sym].options
|
260
|
+
owners[i] = r[:class_name].nil? ? eval(owners[i-1].to_s.gsub(/\w*$/, "")+tname.singularize.camelize) : Util::Common.str2class(r[:class_name])
|
261
|
+
end
|
262
|
+
end
|
263
|
+
owners.reverse!
|
264
|
+
tables.reverse!
|
265
|
+
tables.each_index do |i|
|
266
|
+
if tables[i][-1, 1] == "_"
|
267
|
+
tables[i].reverse!.sub!("_", "").reverse!
|
268
|
+
@inner_joins << transfer_table_name(tables[i], owners[i])
|
269
|
+
end
|
270
|
+
end
|
271
|
+
#process special id field in a belongs_to association
|
272
|
+
if field == "id"
|
273
|
+
if owners[0].reflections[tables[0].to_sym].macro.to_s == "belongs_to"
|
274
|
+
result = transfer_table_id(tables[0], owners[0])
|
275
|
+
@inner_joins.delete(tables[0]) if @inner_joins.include?(tables[0])
|
276
|
+
unless owners[1].nil?
|
277
|
+
result = transfer_table_name(tables[1], owners[1]) + "." + result
|
278
|
+
end
|
279
|
+
tables.delete_at(0)
|
280
|
+
owners.delete_at(0)
|
281
|
+
return result if tables.empty?
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
#get include
|
286
|
+
if tables.length == 1 && tables[0] != @table_name
|
287
|
+
@new_include << tables[0].to_sym unless @new_include.include?(tables[0].to_sym)
|
288
|
+
else
|
289
|
+
tables_clone = tables[0..-1]
|
290
|
+
value = tables_clone.shift.to_sym
|
291
|
+
until tables_clone.empty?
|
292
|
+
hashes = {}
|
293
|
+
hashes[tables_clone.shift.to_sym] = value
|
294
|
+
value = hashes
|
295
|
+
end
|
296
|
+
@new_include << hashes unless hashes.nil? || @new_include.include?(hashes)
|
297
|
+
end
|
298
|
+
result ||= transfer_table_name(tables[0], owners[0]) + "." + field
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
def transfer_table_name(name, owner = @owner)
|
303
|
+
owner.reflections[name.to_sym].class_name.gsub(/([\w]+::)*/, "").pluralize.underscore
|
304
|
+
end
|
305
|
+
|
306
|
+
def transfer_table_id(name, owner = @owner)
|
307
|
+
owner.reflections[name.to_sym].class_name.gsub(/([\w]+::)*/, "").underscore + "_id"
|
308
|
+
end
|
309
|
+
|
310
|
+
def cut_end_underscore(str)
|
311
|
+
str = str.reverse.sub("_", "").reverse if str[-1, 1] == "_"
|
312
|
+
str
|
313
|
+
end
|
314
|
+
|
315
|
+
def cut_end_underscore!(str)
|
316
|
+
str.reverse!.sub!("_", "").reverse! if str[-1, 1] == "_"
|
317
|
+
end
|
318
|
+
end
|
data/test/db.rquerypad
ADDED
Binary file
|
@@ -0,0 +1,25 @@
|
|
1
|
+
reply_one:
|
2
|
+
id: 1
|
3
|
+
title: "rquerypad"
|
4
|
+
thread_id: 1
|
5
|
+
user_id: 1
|
6
|
+
|
7
|
+
reply_two:
|
8
|
+
id: 2
|
9
|
+
title: "thank you"
|
10
|
+
thread_id: 2
|
11
|
+
user_id: 2
|
12
|
+
|
13
|
+
reply_three:
|
14
|
+
id: 3
|
15
|
+
title: "thank you-1"
|
16
|
+
thread_id: 2
|
17
|
+
parent_id: 2
|
18
|
+
user_id: 2
|
19
|
+
|
20
|
+
reply_four:
|
21
|
+
id: 4
|
22
|
+
title: "thank you-2"
|
23
|
+
thread_id: 2
|
24
|
+
parent_id: 2
|
25
|
+
user_id: 2
|
@@ -0,0 +1,17 @@
|
|
1
|
+
thread_one:
|
2
|
+
id: 1
|
3
|
+
title: "welcome"
|
4
|
+
user_id: 1
|
5
|
+
created_at: "2008-03-16 00:00:00"
|
6
|
+
|
7
|
+
thread_two:
|
8
|
+
id: 2
|
9
|
+
title: "join us"
|
10
|
+
user_id: 2
|
11
|
+
created_at: "2008-03-17 00:00:00"
|
12
|
+
|
13
|
+
thread_three:
|
14
|
+
id: 3
|
15
|
+
title: "hello"
|
16
|
+
user_id: 2
|
17
|
+
created_at: "2008-03-16 20:00:00"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateThreads < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :threads do |t|
|
4
|
+
t.string "title"
|
5
|
+
t.string "content"
|
6
|
+
t.integer "user_id"
|
7
|
+
t.datetime "created_at"
|
8
|
+
t.datetime "updated_at"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :threads
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateReplies < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :replies do |t|
|
4
|
+
t.string "title"
|
5
|
+
t.string "content"
|
6
|
+
t.integer "user_id"
|
7
|
+
t.integer "thread_id"
|
8
|
+
t.integer "parent_id"
|
9
|
+
t.datetime "created_at"
|
10
|
+
t.datetime "updated_at"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :replies
|
16
|
+
end
|
17
|
+
end
|
data/test/model.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Model
|
2
|
+
class User < ActiveRecord::Base
|
3
|
+
has_many :threads
|
4
|
+
has_many :replies
|
5
|
+
end
|
6
|
+
class Thread < ActiveRecord::Base
|
7
|
+
has_many :replies
|
8
|
+
belongs_to :user
|
9
|
+
end
|
10
|
+
class Reply < ActiveRecord::Base
|
11
|
+
belongs_to :thread
|
12
|
+
has_many :replies
|
13
|
+
belongs_to :reply, :foreign_key => "parent_id"
|
14
|
+
belongs_to :user
|
15
|
+
acts_as_tree
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
require 'rquerypad'
|
5
|
+
require 'test/model'
|
6
|
+
class RquerypadTest < Test::Unit::TestCase
|
7
|
+
# Replace this with your real tests.
|
8
|
+
include Model
|
9
|
+
def test_this_plugin
|
10
|
+
@fixture_path = 'vendor/plugins/rquerypad/test/fixtures'
|
11
|
+
|
12
|
+
@users = User.find(:all, :group => ["threads.created_at", "name"])
|
13
|
+
assert_equal @users.size, 2
|
14
|
+
|
15
|
+
c = User.count(:conditions => ["threads_.replies.title = ?", "rquerypad"])
|
16
|
+
assert_equal c, 1
|
17
|
+
|
18
|
+
@users = User.find(:all, :conditions => ["threads.replies.title = ? and threads.id = ?", "rquerypad", 1])
|
19
|
+
assert_equal @users.size, 1
|
20
|
+
|
21
|
+
@replies = Reply.find(:all, :conditions => ["thread.id = ?", 2])
|
22
|
+
assert_equal @replies.size, 3
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
3
|
+
|
4
|
+
require 'rquerypad'
|
5
|
+
|
6
|
+
ENV["RAILS_ENV"] = "rquerypad"
|
7
|
+
#require File.expand_path(File.dirname(__FILE__) + "/../../../../config/environment")
|
8
|
+
require 'test_help'
|
9
|
+
Test::Unit::TestCase.fixture_path = File.expand_path(File.dirname(__FILE__) + "/fixtures/")
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
# Transactional fixtures accelerate your tests by wrapping each test method
|
12
|
+
# in a transaction that's rolled back on completion. This ensures that the
|
13
|
+
# test database remains unchanged so your fixtures don't have to be reloaded
|
14
|
+
# between every test method. Fewer database queries means faster tests.
|
15
|
+
#
|
16
|
+
# Read Mike Clark's excellent walkthrough at
|
17
|
+
# http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
|
18
|
+
#
|
19
|
+
# Every Active Record database supports transactions except MyISAM tables
|
20
|
+
# in MySQL. Turn off transactional fixtures in this case; however, if you
|
21
|
+
# don't care one way or the other, switching from MyISAM to InnoDB tables
|
22
|
+
# is recommended.
|
23
|
+
#
|
24
|
+
# The only drawback to using transactional fixtures is when you actually
|
25
|
+
# need to test transactions. Since your test is bracketed by a transaction,
|
26
|
+
# any transactions started in your code will be automatically rolled back.
|
27
|
+
self.use_transactional_fixtures = true
|
28
|
+
|
29
|
+
# Instantiated fixtures are slow, but give you @david where otherwise you
|
30
|
+
# would need people(:david). If you don't want to migrate your existing
|
31
|
+
# test cases which use the @david style and don't mind the speed hit (each
|
32
|
+
# instantiated fixtures translates to a database query per test method),
|
33
|
+
# then set this back to true.
|
34
|
+
self.use_instantiated_fixtures = false
|
35
|
+
|
36
|
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
37
|
+
#
|
38
|
+
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
39
|
+
# -- they do not yet inherit this setting
|
40
|
+
fixtures :all
|
41
|
+
|
42
|
+
# Add more helper methods to be used by all tests here...
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rquerypad
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.11
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Leon Li
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-05-04 00:00:00 +08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Simplify query options with association automation and improve inner join for activerecord of rails
|
17
|
+
email: scorpio_leon@hotmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- CHANGELOG
|
26
|
+
- lib
|
27
|
+
- MIT-LICENSE
|
28
|
+
- pkg
|
29
|
+
- Rakefile
|
30
|
+
- README
|
31
|
+
- tasks
|
32
|
+
- test
|
33
|
+
- lib/rquerypad.rb
|
34
|
+
- test/db.rquerypad
|
35
|
+
- test/fixtures
|
36
|
+
- test/fixtures/replies.yml
|
37
|
+
- test/fixtures/threads.yml
|
38
|
+
- test/fixtures/users.yml
|
39
|
+
- test/migrate
|
40
|
+
- test/migrate/001_create_users.rb
|
41
|
+
- test/migrate/002_create_threads.rb
|
42
|
+
- test/migrate/003_create_replies.rb
|
43
|
+
- test/model.rb
|
44
|
+
- test/rquerypad_test.rb
|
45
|
+
- test/test_helper.rb
|
46
|
+
- tasks/rquerypad_tasks.rake
|
47
|
+
has_rdoc: false
|
48
|
+
homepage:
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements:
|
67
|
+
- none
|
68
|
+
rubyforge_project: rquerypad
|
69
|
+
rubygems_version: 1.0.1
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: Simplify query options with association automation and improve inner join for activerecord of rails
|
73
|
+
test_files: []
|
74
|
+
|