find_by_sql_file 0.9.9

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