find_by_sql_file 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Jordi Bunster <jordi@bunster.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,95 @@
1
+ = Find by SQL file
2
+
3
+ This plugin extends the API of ActiveRecord::Base#find_by_sql
4
+
5
+ == A Warning
6
+
7
+ Besides the warnings on the +ERB+ section below (don't ignore those), this
8
+ code has very little production track record. So there, beware.
9
+
10
+ == Example Usage
11
+
12
+ Instead of passing the SQL statement as a string...
13
+
14
+ Elephant.find_by_sql "SELECT * FROM elephants WHERE weight='massive'"
15
+
16
+ You can pass a symbol that refers to a query file:
17
+
18
+ Elephant.find_by_sql :massive_weight
19
+
20
+ Query files are assumed to be save as:
21
+
22
+ Rails.root/app/queries/TABLE_NAME/SYMBOL.sql
23
+
24
+ == Installation instructions
25
+
26
+ Via rubygems:
27
+
28
+ gem install find_by_sql_file
29
+
30
+ # Rails 2.x (config/environment.rb)
31
+ config.gem 'find_by_sql_file'
32
+
33
+ # Rails 3.x (Gemfile)
34
+ gem 'find_by_sql_file'
35
+
36
+ # Or, to use outside of Rails, for now (better API coming soon):
37
+ require 'active_record'
38
+ RAILS_ROOT = '/some/folder/'
39
+ require 'find_by_sql_file'
40
+
41
+ == Motivation
42
+
43
+ The advantage of the external file approach is that the SQL file can be
44
+ properly indented and commented (the indentation and comments are stripped
45
+ from the logs.)
46
+
47
+ == Features & Problems
48
+
49
+ === Comment removal
50
+
51
+ As far as comment removal, only double-dash-space single-line comments are
52
+ stripped, like so:
53
+
54
+ SELECT foo, -- We need this for X reason
55
+ bar, -- and this for some Y reason
56
+ bez, # This comment will NOT be removed, and will be a problem
57
+ duh /* And neither will this one. Use -- style only */
58
+
59
+ FROM table;
60
+
61
+ So, to clarify, the start-comment marker is '-- ' (two dashes and a space).
62
+ That I know of, this marker works in MySQL, PostgreSQL, SQLite, Oracle, DB2,
63
+ and SQL Server. While not all of these require the space after the dashes,
64
+ it never hurts.
65
+
66
+ === Bind variables
67
+
68
+ It's possible to pass named bind variables, much like in the conditions
69
+ parameter of ActiveRecord::Base.find, by passing a hash as the second
70
+ parameter, like so:
71
+
72
+ Elephant.find_by_sql :specifics, :color => 'grey', :weight => 6800
73
+
74
+ === ERB (be careful)
75
+
76
+ It is also possible to use +ERB+ inside the query file, but *beware!*
77
+ Unlike the named bind variables, any data passed in via the ERB method is not
78
+ properly quoted by the database adapter, leaving open the possibility of
79
+ *SQL injection*. 99.9% of the time, *you will _NOT_ need this*.
80
+
81
+ Here's an artificial (but easy to explain) example of how the
82
+ (very dangerous!) +ERB+ _feature_ works:
83
+
84
+ Elephant.find_by_sql :single_value, :value => 'grey',
85
+ :inject! => { :field => 'color' }
86
+
87
+ The call above replaces the bind variable +value+ inside the SQL file,
88
+ but it also populates the instance variable +field+ with "+color+", which
89
+ can then be used with the usual ERB syntax, like so:
90
+
91
+ SELECT <%= @field -%> FROM elephants WHERE <%= @field -%> = :value
92
+
93
+ == Legal
94
+
95
+ Copyright (c) 2008..2010 Jordi Bunster, released under the MIT license
@@ -0,0 +1,42 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the find_by_sql_file plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the find_by_sql_file plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'FindBySqlFile'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include 'README.rdoc'
21
+ rdoc.rdoc_files.include 'lib/**/*.rb'
22
+ end
23
+
24
+ namespace :rcov do
25
+ begin
26
+ require 'rcov/rcovtask'
27
+ rescue LoadError
28
+ desc '(Not installed!)'
29
+ task :build do
30
+ puts "Install 'rcov' to get test coverage reports."
31
+ end
32
+ else
33
+ Rcov::RcovTask.new(:build) do |t|
34
+ t.test_files = %w[ test/*_test.rb ]
35
+ t.output_dir = 'coverage'
36
+ t.verbose = true
37
+ t.rcov_opts << '-x /Library -x ~/.gem -x /usr --html'
38
+ end
39
+ end
40
+ end
41
+
42
+ task :rcov => :'rcov:build'
@@ -0,0 +1,12 @@
1
+ * 0.9.9 (2010-02-21):
2
+
3
+ - Changed the comment marker to be '-- ' (two dashes and a space)
4
+ - Tested under Ruby 1.8.7 and 1.9.1
5
+ - Tested under ActiveRecord 2.3 and 3.0beta
6
+
7
+ * 0.9.5 (2008-10-06):
8
+
9
+ - Added a WHATSNEW file :)
10
+ - Added a few behaviour tests
11
+ - Added ActiveRecord::Base#find_one_by_sql(*args) as sugar for
12
+ ActiveRecord::Base#find_by_sql(*args).first
@@ -0,0 +1,45 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'find_by_sql_file'
3
+ s.version = '0.9.9'
4
+ s.date = '2010-2-21'
5
+
6
+ s.author = 'Jordi Bunster'
7
+ s.email = 'jordi@bunster.org'
8
+ s.homepage = 'http://github.com/jordi/find_by_sql_file'
9
+
10
+ s.summary = "Extension to ActiveRecord's find_by_sql to use SQL files"
11
+ s.description = %{ This ActiveRecord extension lets you pass a symbol to
12
+ ActiveRecord::Base#find_by_sql, which refers to a query
13
+ file saved under app/queries/. Variable replacement
14
+ and ERB are available, and comments and indentation are
15
+ stripped. }.strip!.gsub! /\s+/, ' '
16
+
17
+ s.test_files = %w[
18
+ test
19
+ test/find_by_sql_file_test.rb
20
+ test/rails_root_fixture
21
+ test/rails_root_fixture/app
22
+ test/rails_root_fixture/app/queries
23
+ test/rails_root_fixture/app/queries/articles
24
+ test/rails_root_fixture/app/queries/articles/all.sql
25
+ test/rails_root_fixture/app/queries/articles/count_all.sql
26
+ test/rails_root_fixture/app/queries/articles/last_updated.sql
27
+ test/rails_root_fixture/app/queries/articles/with_erb_fields.sql
28
+ test/rails_root_fixture/app/queries/articles/with_variables.sql
29
+ test/test_helper.rb
30
+ ]
31
+
32
+ s.files = %w[ MIT-LICENSE
33
+ README.rdoc
34
+ Rakefile
35
+ WHATSNEW
36
+ find_by_sql_file.gemspec
37
+ lib
38
+ lib/find_by_sql_file.rb
39
+ rails
40
+ rails/init.rb ]
41
+
42
+ s.has_rdoc = true
43
+ s.extra_rdoc_files = %w[ README.rdoc ]
44
+ s.rdoc_options = %w[ --main README.rdoc ]
45
+ end
@@ -0,0 +1,64 @@
1
+ require 'erb'
2
+ require 'active_record'
3
+
4
+ module FindBySqlFile
5
+ # This makes it a tad simpler to create a clean ERB binding context. It
6
+ # works like so:
7
+ #
8
+ # ERBJacket.wrap "A Foo is: <%= @foo -%>", :foo => 'not a bar'
9
+ #
10
+ class ERBJacket
11
+ def initialize(hash) # :nodoc:
12
+ hash.each { |k, v| instance_variable_set('@' + k.to_s, v) }
13
+ end
14
+
15
+ def wrap(erb_template) # :nodoc:
16
+ ::ERB.new(erb_template, nil, '-').result binding
17
+ end
18
+
19
+ def self.wrap(erb_template = '', instance_variables = {}) # :nodoc:
20
+ new(instance_variables).wrap erb_template
21
+ end
22
+ end
23
+
24
+ def find_by_sql_with_sql_file(query_or_symbol, opts = {}) # :nodoc:
25
+ find_by_sql_without_sql_file sql_file(query_or_symbol, opts)
26
+ end
27
+
28
+ def count_by_sql_with_sql_file(query_or_symbol, opts = {}) # :nodoc:
29
+ count_by_sql_without_sql_file sql_file(query_or_symbol, opts)
30
+ end
31
+
32
+ def sql_file(query_or_symbol, opts = {}) # :nodoc:
33
+ if query_or_symbol.is_a? Symbol
34
+
35
+ file_name = Rails.root.join 'app', 'queries',
36
+ (table_name rescue 'application'), (query_or_symbol.to_s + '.sql')
37
+
38
+ bound_variables = HashWithIndifferentAccess.new(opts).symbolize_keys!
39
+ injected_locals = bound_variables.delete(:inject!) || []
40
+
41
+ query = ERBJacket.wrap File.read(file_name), injected_locals
42
+ query = replace_named_bind_variables(query, bound_variables)
43
+
44
+ query.gsub(/-- .*/, '').strip.gsub(/\s+/, ' ')
45
+ else
46
+ raise 'Additional parameters only supported when using a query' \
47
+ ' file (pass a symbol, not a string)' unless opts.blank?
48
+
49
+ query_or_symbol
50
+ end
51
+ end
52
+
53
+ # The equivalent of ActiveRecord::Base#find_by_sql(*args).first
54
+ def find_one_by_sql(*args)
55
+ find_by_sql(*args).first
56
+ end
57
+ end
58
+
59
+ class << ActiveRecord::Base
60
+ include FindBySqlFile
61
+
62
+ alias_method_chain :find_by_sql, :sql_file
63
+ alias_method_chain :count_by_sql, :sql_file
64
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'find_by_sql_file')
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Article < ActiveRecord::Base
4
+ end
5
+
6
+ class FindBySqlFileTest < Test::Unit::TestCase
7
+
8
+ def test_counting_by_sql_file
9
+ assert_equal Article.count_by_sql(:count_all), Article.count
10
+ end
11
+
12
+ def test_find_all
13
+ assert_equal Article.find_by_sql(:all), Article.find(:all)
14
+ end
15
+
16
+ def test_find_last_updated
17
+ assert_equal Article.find_one_by_sql(:last_updated),
18
+ Article.find(:first, :order => 'updated_at DESC')
19
+ end
20
+
21
+ def test_named_bind_variables
22
+ assert_equal Article.find_by_sql(:with_variables,
23
+ :published => @articles.last.published,
24
+ :created_on => @articles.last.created_on),
25
+ Article.find(:all, :conditions => {
26
+ :published => @articles.last.published,
27
+ :created_on => @articles.last.created_on })
28
+ end
29
+
30
+ def test_erb_injection
31
+ fields = 'id, title, published'
32
+
33
+ assert_equal(
34
+ Article.find_by_sql(:with_erb_fields,
35
+ :inject! => { :select => fields }).map { |a| a.attributes },
36
+ Article.find(:all, :select => fields).map { |a| a.attributes } )
37
+ end
38
+
39
+ def test_query_reformatting
40
+ assert_equal 'SELECT * FROM articles ORDER BY updated_at DESC LIMIT 1',
41
+ Article.sql_file(:last_updated)
42
+ end
43
+
44
+ def setup
45
+ @articles = []
46
+
47
+ @articles << Article.new(:title => 'Lorem ipsum dolor sit amet',
48
+ :published => true,
49
+ :created_on => '1969-01-27',
50
+ :updated_at => '2008-10-06 12:02:32')
51
+
52
+ @articles << Article.new(:title => 'Consectetur adipisicing elit',
53
+ :published => false,
54
+ :created_on => '1969-12-30',
55
+ :updated_at => '2008-10-06 12:02:32')
56
+
57
+ @articles << Article.new(:title => 'Sed do eiusmod tempor incididunt',
58
+ :published => false,
59
+ :created_on => '1969-01-15',
60
+ :updated_at => '2008-10-06 12:02:32')
61
+
62
+ @articles << Article.new(:title => 'Ut enim ad minim veniam',
63
+ :published => false,
64
+ :created_on => '1999-12-13',
65
+ :updated_at => '2012-10-06 12:02:32')
66
+
67
+ @articles.each { |a| a.save }
68
+ end
69
+
70
+ def teardown
71
+ Article.delete_all
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ -- This query gets all of the articles
2
+ SELECT * FROM articles
3
+
@@ -0,0 +1,2 @@
1
+ -- Count all of the articles using SQL
2
+ SELECT COUNT(*) FROM articles
@@ -0,0 +1,3 @@
1
+ SELECT * FROM articles
2
+ ORDER BY updated_at DESC -- Gets the last one updated
3
+ LIMIT 1
@@ -0,0 +1,3 @@
1
+ -- Gets certain fields from articles using ERB to pass the SELECT fragment
2
+
3
+ SELECT <%= @select -%> FROM articles
@@ -0,0 +1,4 @@
1
+ -- Gets articles with given values for 'created_on' and 'published'
2
+
3
+ SELECT * FROM articles WHERE created_on = :created_on
4
+ AND published = :published
@@ -0,0 +1,30 @@
1
+ require 'test/unit'
2
+ require 'yaml'
3
+ require 'pathname'
4
+ require 'rubygems'
5
+
6
+ begin
7
+ require 'leftright'
8
+ rescue LoadError
9
+ puts "Install the 'leftright' gem (optional) to get awesome test output"
10
+ end
11
+
12
+ module Rails
13
+ def self.root
14
+ Pathname.new File.join(File.dirname(__FILE__), 'rails_root_fixture')
15
+ end
16
+ end
17
+
18
+ require 'find_by_sql_file'
19
+
20
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
21
+ :database => ':memory:'
22
+
23
+ ActiveRecord::Schema.define :version => 1 do
24
+ create_table :articles, :force => true do |article|
25
+ article.string :title
26
+ article.boolean :published
27
+ article.date :created_on
28
+ article.timestamp :updated_at
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: find_by_sql_file
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.9
5
+ platform: ruby
6
+ authors:
7
+ - Jordi Bunster
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-21 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: This ActiveRecord extension lets you pass a symbol to ActiveRecord::Base#find_by_sql, which refers to a query file saved under app/queries/. Variable replacement and ERB are available, and comments and indentation are stripped.
17
+ email: jordi@bunster.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.rdoc
27
+ - Rakefile
28
+ - WHATSNEW
29
+ - find_by_sql_file.gemspec
30
+ - lib/find_by_sql_file.rb
31
+ - rails/init.rb
32
+ has_rdoc: true
33
+ homepage: http://github.com/jordi/find_by_sql_file
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --main
39
+ - README.rdoc
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.3.5
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Extension to ActiveRecord's find_by_sql to use SQL files
61
+ test_files:
62
+ - test/find_by_sql_file_test.rb
63
+ - test/rails_root_fixture/app/queries/articles/all.sql
64
+ - test/rails_root_fixture/app/queries/articles/count_all.sql
65
+ - test/rails_root_fixture/app/queries/articles/last_updated.sql
66
+ - test/rails_root_fixture/app/queries/articles/with_erb_fields.sql
67
+ - test/rails_root_fixture/app/queries/articles/with_variables.sql
68
+ - test/test_helper.rb