ruport 1.2.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,33 +0,0 @@
1
- #--
2
- # sqlsplit.rb : A tool to properly split SQL input
3
- #
4
- # This is Free Software. You may freely redistribute or modify under the terms
5
- # of the GNU General Public License or the Ruby License. See LICENSE and
6
- # COPYING for details.
7
- #
8
- # Created by Francis Hwang, 2005.12.31
9
- # Copyright (c) 2005, All Rights Reserved.
10
- #++
11
- module Ruport
12
- class Query
13
- # This class properly splits up multi-statement SQL input for use with
14
- # Ruby/DBI
15
- class SqlSplit < Array #:nodoc:
16
- def initialize( sql )
17
- super()
18
- next_sql = ''
19
- sql.each do |line|
20
- unless line =~ /^--/ or line =~ %r{^/\*.*\*/;} or line =~ /^\s*$/
21
- next_sql << line
22
- if line =~ /;$/
23
- next_sql.gsub!( /;\s$/, '' )
24
- self << next_sql
25
- next_sql = ''
26
- end
27
- end
28
- end
29
- self << next_sql if next_sql != ''
30
- end
31
- end
32
- end
33
- end
data/lib/ruport/query.rb DELETED
@@ -1,232 +0,0 @@
1
- # Ruport : Extensible Reporting System
2
- #
3
- # query.rb provides a basic wrapper around RubyDBI for SQL interaction
4
- #
5
- # Original work began by Gregory Brown based on ideas from James Edward Gray II
6
- # in August, 2005.
7
- #
8
- # Copyright (C) 2005-2007, Gregory Brown
9
- # All Rights Reserved.
10
- #
11
- # This is free software distributed under the same terms as Ruby 1.8
12
- # See LICENSE and COPYING for details.
13
- require "generator"
14
- require "ruport/query/sql_split"
15
-
16
- module Ruport
17
-
18
- # === Overview
19
- #
20
- # Query offers a way to interact with databases via RubyDBI. It supports
21
- # returning result sets in either Ruport's Data::Table, or in their
22
- # raw form as DBI::Rows.
23
- #
24
- # Query allows you to treat your result sets as an Enumerable data structure
25
- # that plays well with the rest of Ruport.
26
- #
27
- # If you are using ActiveRecord, you might prefer our acts_as_reportable
28
- # extension.
29
- #
30
- class Query
31
-
32
- include Enumerable
33
-
34
- # Ruport::Query provides an interface for dealing with raw SQL queries.
35
- # The SQL can be single or multistatement, but the resulting Data::Table
36
- # will consist only of the result of the last statement.
37
- #
38
- # Available options:
39
- #
40
- # <b><tt>:source</tt></b>:: A source specified in
41
- # Ruport::Query.sources, defaults to
42
- # <tt>:default</tt>.
43
- # <b><tt>:dsn</tt></b>:: If specifed, the Query object will
44
- # manually override Ruport::Query.
45
- # <b><tt>:user</tt></b>:: If a DSN is specified, the user can
46
- # be set with this option.
47
- # <b><tt>:password</tt></b>:: If a DSN is specified, the password
48
- # can be set with this option.
49
- # <b><tt>:row_type</tt></b>:: When set to :raw, DBI::Rows will be
50
- # returned instead of a Data::Table
51
- #
52
- # Examples:
53
- #
54
- # # uses Ruport::Query's default source
55
- # Ruport::Query.new("select * from fo")
56
- #
57
- # # uses the Ruport::Query's source labeled :my_source
58
- # Ruport::Query.new("select * from fo", :source => :my_source)
59
- #
60
- # # uses a manually entered source
61
- # Ruport::Query.new("select * from fo", :dsn => "dbi:mysql:my_db",
62
- # :user => "greg", :password => "chunky_bacon" )
63
- #
64
- # # uses a SQL file stored on disk
65
- # Ruport::Query.new("my_query.sql")
66
- #
67
- # # explicitly use a file, even if it doesn't end in .sql
68
- # Ruport::Query.new(:file => "foo")
69
- #
70
- def initialize(sql, options={})
71
- if sql.kind_of?(Hash)
72
- options = { :source => :default }.merge(sql)
73
- sql = options[:file] || options[:string]
74
- else
75
- options = { :source => :default, :string => sql }.merge(options)
76
- options[:file] = sql if sql =~ /.sql$/
77
- end
78
- origin = options[:file] ? :file : :string
79
-
80
- @statements = SqlSplit.new(get_query(origin,sql))
81
- @sql = @statements.join
82
-
83
- if options[:dsn]
84
- Ruport::Query.add_source :temp, :dsn => options[:dsn],
85
- :user => options[:user],
86
- :password => options[:password]
87
- options[:source] = :temp
88
- end
89
-
90
- select_source(options[:source])
91
-
92
- @raw_data = options[:row_type].eql?(:raw)
93
- @params = options[:params]
94
- end
95
-
96
- # Returns an OpenStruct with the configuration options for the default
97
- # database source.
98
- #
99
- def self.default_source
100
- sources[:default]
101
- end
102
-
103
- # Returns a hash of database sources, keyed by label.
104
- def self.sources
105
- @sources ||= {}
106
- end
107
-
108
- # Allows you to add a labeled DBI source configuration.
109
- #
110
- # Query objects will use the source labeled <tt>:default</tt>,
111
- # unless another source is specified.
112
- #
113
- # Examples:
114
- #
115
- # # a connection to a MySQL database foo with user root, pass chunkybacon
116
- # Query.add_source :default, :dsn => "dbi:mysql:foo",
117
- # :user => "root",
118
- # :password => "chunkybacon"
119
- #
120
- #
121
- # # a second connection to a MySQL database bar
122
- # Query.add_source :test, :dsn => "dbi:mysql:bar",
123
- # :user => "tester",
124
- # :password => "blinky"
125
- #
126
- #
127
- def self.add_source(name,options={})
128
- sources[name] = OpenStruct.new(options)
129
- check_source(sources[name],name)
130
- end
131
-
132
- attr_accessor :raw_data
133
-
134
- # The original SQL for the Query object
135
- attr_reader :sql
136
-
137
- # This will set the <tt>dsn</tt>, <tt>username</tt>, and <tt>password</tt>
138
- # to one specified by a source in Ruport::Query.
139
- #
140
- def select_source(label)
141
- @dsn = Ruport::Query.sources[label].dsn
142
- @user = Ruport::Query.sources[label].user
143
- @password = Ruport::Query.sources[label].password
144
- end
145
-
146
- # Yields result set by row.
147
- def each(&action)
148
- raise(LocalJumpError, "No block given!") unless action
149
- fetch(&action)
150
- self
151
- end
152
-
153
- # Runs the SQL query and returns the result set
154
- def result; fetch; end
155
-
156
- # Runs the query without returning its results.
157
- def execute; fetch; nil; end
158
-
159
- # Returns a Data::Table, even if in <tt>raw_data</tt> mode.
160
- def to_table
161
- data_flag, @raw_data = @raw_data, false
162
- data = fetch; @raw_data = data_flag; return data
163
- end
164
-
165
- # Returns a csv dump of the query.
166
- def to_csv
167
- fetch.to_csv
168
- end
169
-
170
- # Returns a Generator object of the result set.
171
- def generator
172
- Generator.new(fetch)
173
- end
174
-
175
- private
176
-
177
- def query_data(query_text, params=@params)
178
-
179
- require "dbi"
180
-
181
- data = @raw_data ? [] : Data::Table.new
182
-
183
- DBI.connect(@dsn, @user, @password) do |dbh|
184
- dbh.execute(query_text, *(params || [])) do |sth|
185
- # Work-around for inconsistent DBD behavior w/ resultless queries
186
- names = sth.column_names rescue []
187
- if names.empty?
188
- # Work-around for SQLite3 DBD bug
189
- sth.cancel rescue nil
190
- return nil
191
- end
192
-
193
- data.column_names = names unless @raw_data
194
-
195
- sth.each do |row|
196
- row = row.to_a
197
- row = Data::Record.new(row, :attributes => names) unless @raw_data
198
- yield row if block_given?
199
- data << row if !block_given?
200
- end
201
- end
202
- end
203
- data
204
- end
205
-
206
- def get_query(type,query)
207
- type.eql?(:file) ? load_file( query ) : query
208
- end
209
-
210
- def fetch(&block)
211
- data = nil
212
- final = @statements.size - 1
213
- @statements.each_with_index do |query_text, index|
214
- data = query_data(query_text, &(index == final ? block : nil))
215
- end
216
- return data
217
- end
218
-
219
- def load_file(query_file)
220
- begin
221
- File.read( query_file ).strip
222
- rescue
223
- raise LoadError, "Could not open #{query_file}"
224
- end
225
- end
226
-
227
- def self.check_source(settings,label) # :nodoc:
228
- raise ArgumentError unless settings.dsn
229
- end
230
-
231
- end
232
- end
@@ -1,272 +0,0 @@
1
- #!/usr/bin/env ruby -w
2
- require File.join(File.expand_path(File.dirname(__FILE__)), "helpers")
3
-
4
- begin
5
- require "mocha"
6
- require "stubba"
7
- Ruport.quiet { require "active_record" }
8
- rescue LoadError
9
- nil
10
- end
11
-
12
- if Object.const_defined?(:ActiveRecord) && Object.const_defined?(:Mocha)
13
-
14
- require "ruport/acts_as_reportable"
15
-
16
- class Team < ActiveRecord::Base
17
- acts_as_reportable :except => 'id', :include => :players
18
- has_many :players
19
- end
20
-
21
- class Player < ActiveRecord::Base
22
- acts_as_reportable
23
- belongs_to :team
24
- belongs_to :personal_trainer
25
-
26
- def stats
27
- "#{name} stats"
28
- end
29
- end
30
-
31
- module SomeModule
32
- class PersonalTrainer < ActiveRecord::Base
33
- acts_as_reportable
34
- has_one :team
35
- has_many :players
36
- end
37
- end
38
-
39
- module ModelStubsSetup
40
- Column = ActiveRecord::ConnectionAdapters::Column
41
- PersonalTrainer = SomeModule::PersonalTrainer
42
-
43
- def setup
44
- setup_column_stubs
45
-
46
- @trainers = []
47
- @trainers << PersonalTrainer.new(:name => "Trainer 1")
48
- @trainers << PersonalTrainer.new(:name => "Trainer 2")
49
- @teams = []
50
- @teams << Team.new( :name => "Testers",
51
- :league => "My League")
52
- @teams << Team.new( :name => "Others",
53
- :league => "Other League")
54
- @players = []
55
- @players << Player.new( :team_id => 1,
56
- :name => "Player 1",
57
- :personal_trainer_id => 1)
58
- @players << Player.new( :team_id => 1,
59
- :name => "Player 2",
60
- :personal_trainer_id => 2)
61
-
62
- setup_find_stubs
63
- end
64
-
65
- private
66
-
67
- def setup_column_stubs
68
- PersonalTrainer.stubs(:columns).returns([
69
- Column.new("id", nil, "integer", false),
70
- Column.new("name", nil, "string", false)])
71
- Team.stubs(:columns).returns([Column.new("id", nil, "integer", false),
72
- Column.new("name", nil, "string", false),
73
- Column.new("league", nil, "string", true)])
74
- Player.stubs(:columns).returns([Column.new("id", nil, "integer", false),
75
- Column.new("team_id", nil, "integer", true),
76
- Column.new("name", nil, "string", false),
77
- Column.new("personal_trainer_id", nil, "integer", true)])
78
- end
79
-
80
- def setup_find_stubs
81
- PersonalTrainer.stubs(:find).returns(@trainers)
82
- @trainers[0].stubs(:players).returns([@players[0]])
83
- @trainers[1].stubs(:players).returns([@players[1]])
84
- Team.stubs(:find).returns(@teams)
85
- @teams[0].stubs(:players).returns(@players)
86
- @teams[1].stubs(:players).returns([])
87
- Player.stubs(:find).returns(@players)
88
- Player.stubs(:find_by_sql).returns(@players)
89
- @players[0].stubs(:team).returns(@teams[0])
90
- @players[1].stubs(:team).returns(@teams[0])
91
- @players[0].stubs(:personal_trainer).returns(@trainers[0])
92
- @players[1].stubs(:personal_trainer).returns(@trainers[1])
93
- end
94
- end
95
-
96
-
97
- class TestActsAsReportableClassMethods < Test::Unit::TestCase
98
-
99
- def test_aar_options_set
100
- assert_equal({:except => 'id', :include => :players}, Team.aar_options)
101
- end
102
- end
103
-
104
- class TestActsAsReportableSingletonMethods < Test::Unit::TestCase
105
- include ModelStubsSetup
106
-
107
- def test_basic_report_table
108
- actual = Player.report_table
109
- expected = [[1, "Player 1", 1],
110
- [1, "Player 2", 2]].to_table(%w[team_id name personal_trainer_id])
111
- assert_equal expected, actual
112
- end
113
-
114
- def test_report_table_by_sql
115
- actual = Player.report_table_by_sql("SELECT * FROM players")
116
- expected = [[1, "Player 1", 1],
117
- [1, "Player 2", 2]].to_table(%w[team_id name personal_trainer_id])
118
- assert_equal expected, actual
119
- end
120
-
121
- def test_only_option
122
- actual = Player.report_table(:all, :only => 'name')
123
- expected = [["Player 1"],["Player 2"]].to_table(%w[name])
124
- assert_equal expected, actual
125
- end
126
-
127
- def test_only_option_preserves_column_sort_order
128
- column_order = %w[name personal_trainer_id team_id]
129
- actual = Player.report_table(:all, :only => column_order)
130
- expected = [["Player 1", 1, 1],
131
- ["Player 2", 2, 1]].to_table(column_order)
132
- assert_equal expected, actual
133
- end
134
-
135
- def test_except_option
136
- actual = Player.report_table(:all, :except => 'personal_trainer_id')
137
- expected = [[1, "Player 1"],[1, "Player 2"]].to_table(%w[team_id name])
138
- assert_equal expected, actual
139
- end
140
-
141
- def test_methods_option
142
- actual = Player.report_table(:all, :only => 'name', :methods => :stats)
143
- expected = [["Player 1", "Player 1 stats"],
144
- ["Player 2", "Player 2 stats"]].to_table(%w[name stats])
145
- assert_equal expected, actual
146
- end
147
-
148
- def test_include_option
149
- actual = Player.report_table(:all, :only => 'name',
150
- :include => :personal_trainer)
151
- expected = [["Player 1", "Trainer 1"],
152
- ["Player 2", "Trainer 2"]].to_table(%w[name personal_trainer.name])
153
- assert_equal expected, actual
154
- end
155
-
156
- def test_column_sorting_works_with_include_option
157
- actual = Player.report_table(:all,
158
- :only => %w[name personal_trainer.name],
159
- :include => { :personal_trainer => { :only => %w[name] } })
160
- expected = [["Player 1", "Trainer 1"],
161
- ["Player 2", "Trainer 2"]].to_table(%w[name personal_trainer.name])
162
- assert_equal expected, actual
163
-
164
- actual = Player.report_table(:all,
165
- :only => %w[personal_trainer.name name],
166
- :include => { :personal_trainer => { :only => %w[name] } })
167
- expected = [["Trainer 1", "Player 1"],
168
- ["Trainer 2", "Player 2"]].to_table(%w[personal_trainer.name name])
169
- assert_equal expected, actual
170
- end
171
-
172
- def test_include_has_options
173
- actual = Team.report_table(:all, :only => 'name',
174
- :include => { :players => { :only => 'name' } })
175
- expected = [["Testers", "Player 1"],
176
- ["Testers", "Player 2"],
177
- ["Others", nil]].to_table(%w[name players.name])
178
- assert_equal expected, actual
179
- end
180
-
181
- class CustomRecord < Ruport::Data::Record; end
182
-
183
- def test_record_class_option
184
- actual = Player.report_table(:all, :record_class => CustomRecord)
185
- actual.each { |r| assert_equal CustomRecord, r.class }
186
-
187
- actual = Player.report_table_by_sql("SELECT * FROM players",
188
- :record_class => CustomRecord)
189
- actual.each { |r| assert_equal CustomRecord, r.class }
190
- end
191
-
192
- def test_get_include_for_find
193
- assert_equal :players, Team.send(:get_include_for_find, nil)
194
- assert_equal nil, Player.send(:get_include_for_find, nil)
195
- assert_equal :team, Player.send(:get_include_for_find, :team)
196
- expected = {:team => {}}
197
- assert_equal expected,
198
- Player.send(:get_include_for_find, {:team => {:except => 'id'}})
199
- expected = {:team => {:a => {}, :b => {}},
200
- :c => {:d => {:e => {}, :f => {}}},
201
- :g => {}}
202
- assert_equal expected,
203
- Player.send(:get_include_for_find, {:team => {:include => [:a,:b]},
204
- :c => {:include => {:d => {:include => [:e,:f]}}}, :g => {}})
205
- end
206
- end
207
-
208
- class TestActsAsReportableInstanceMethods < Test::Unit::TestCase
209
- include ModelStubsSetup
210
-
211
- def test_reportable_data
212
- actual = @players[0].reportable_data
213
- expected = [{ 'team_id' => 1,
214
- 'name' => "Player 1",
215
- 'personal_trainer_id' => 1 }]
216
- assert_equal expected, actual
217
-
218
- actual = @teams[0].reportable_data(:include =>
219
- { :players => { :only => 'name' } })
220
- expected = [{ 'name' => "Testers",
221
- 'league' => "My League",
222
- 'players.name' => "Player 1" },
223
- { 'name' => "Testers",
224
- 'league' => "My League",
225
- 'players.name' => "Player 2" }]
226
- assert_equal expected, actual
227
- end
228
-
229
- def test_add_includes
230
- actual = @players[0].send(:add_includes,
231
- [{ 'name' => "Player 1" }], :personal_trainer)
232
- expected = [{ 'name' => "Player 1",
233
- 'personal_trainer.name' => "Trainer 1" }]
234
- assert_equal expected, actual
235
- end
236
-
237
- def test_has_report_options
238
- assert @teams[0].send(:has_report_options?, { :only => 'name' })
239
- assert @teams[0].send(:has_report_options?, { :except => 'name' })
240
- assert @teams[0].send(:has_report_options?, { :methods => 'name' })
241
- assert @teams[0].send(:has_report_options?, { :include => 'name' })
242
- assert !@teams[0].send(:has_report_options?, { :foo => 'name' })
243
- end
244
-
245
- def test_get_attributes_with_options
246
- actual = @players[0].send(:get_attributes_with_options)
247
- expected = { 'team_id' => 1,
248
- 'name' => "Player 1",
249
- 'personal_trainer_id' => 1 }
250
- assert_equal expected, actual
251
-
252
- actual = @players[0].send(:get_attributes_with_options,
253
- { :only => 'name' })
254
- expected = { 'name' => "Player 1" }
255
- assert_equal expected, actual
256
-
257
- actual = @players[0].send(:get_attributes_with_options,
258
- { :except => 'personal_trainer_id' })
259
- expected = { 'team_id' => 1,
260
- 'name' => "Player 1" }
261
- assert_equal expected, actual
262
-
263
- actual = @players[0].send(:get_attributes_with_options,
264
- { :only => 'name', :qualify_attribute_names => :players })
265
- expected = { 'players.name' => "Player 1" }
266
- assert_equal expected, actual
267
- end
268
- end
269
-
270
- else
271
- $stderr.puts "Warning: Mocha and/or ActiveRecord not found -- skipping AAR tests"
272
- end