wherex 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # ignore Gemfile.lock because it is rewritten by the tests
18
+ Gemfile.lock
19
+
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ gem 'arel', '~> 2.0'
5
+ gem 'activerecord', '~> 3.0'
6
+
7
+ case ENV["RAILS_DB"]
8
+ when "postgres"
9
+ gem 'pg'
10
+ when "mysql"
11
+ gem 'mysql2'
12
+ else
13
+ gem 'sqlite3'
14
+ end
15
+
16
+ # Add dependencies to develop your gem here.
17
+ # Include everything needed to run rake, tests, features, etc.
18
+ group :development do
19
+ gem "bundler", "~> 1.0.0"
20
+ gem "jeweler", "~> 1.5.2"
21
+ gem "rcov", ">= 0"
22
+ end
23
+
24
+ group :test do
25
+ gem 'redgreen'
26
+ gem 'turn'
27
+ end
28
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jason King
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.md ADDED
@@ -0,0 +1,106 @@
1
+ # wherex
2
+
3
+ Regexp support to ActiveRecord finders.
4
+
5
+ ## Howto?
6
+
7
+ In your Gemfile
8
+
9
+ gem 'wherex'
10
+
11
+ Then run `bundle install`
12
+
13
+ ## What the?
14
+
15
+ # find users in 93, 94 and 95 (5 digit) zipcodes (new style finder)
16
+ User.where :zipcode => /^9[345][0-9]{3}$/
17
+
18
+ # find students with invalid characters in their names (old style finder)
19
+ Student.all :conditions => { :name => /[^a-zA-Z ]/ }
20
+
21
+ # find products with a complex code structure (dynamic finders)
22
+ Product.find_by_code /^[NRW][^-]+-[456]/
23
+
24
+ ## Limitations?
25
+
26
+ ### POSIX Only (mostly)
27
+
28
+ Most DBs only support POSIX regexps!
29
+
30
+ Let me repeat? POSIX only! So, some examples of things to know:
31
+
32
+ * `^` and `$` have their POSIX meanings, so beginning and end of the whole string, not of each line within the string. Also `\A` and `\Z` have no special meaning.
33
+ * Convenience character classes like `\w \W \d \D \s \S`
34
+ * Capturing parens won't break the RE, but there's no capturing (so you can't use what you captured)
35
+ * No extended patterns, so `(?i:foo)` won't work, nor will `(?:bar)` and nor will `(?i)foo`
36
+ * No look-around assertions work, so no `foo(?!bar)`
37
+
38
+ ### SQLite is very slow
39
+
40
+ SQLite's implementation of REGEXP is actually a callback to a user defined
41
+ function, which this gem provides in Ruby. This is **really cool™** because
42
+ SQLite users get to use full Ruby regexps (ie. **none** of the limitations above
43
+ apply) but it comes at a cost.
44
+
45
+ On a relatively small data set (say 1000 rows) my MySQL testing ran through all
46
+ records in 2.3ms whereas SQLite took 84.6ms (because it has to call out to Ruby
47
+ for each row tested).
48
+
49
+ So, don't be surprised if SQLite falls in a heap, and I'd recommend just
50
+ abandoning this idea for any production usage.
51
+
52
+ ### Only SQLite, MySQL and PostgreSQL supported out of the box
53
+
54
+ You can add your own support for any other DB that supports regexp. I know that
55
+ Oracle has a regexp function, and I believe that MSSQL has a regexp XP (a Perl
56
+ RE one actually).
57
+
58
+ Here's how I'd add in Oracle support:
59
+
60
+ # config/initializers/wherex.rb
61
+ module Wherex
62
+ module OracleEnhancedAdapter
63
+ def regexp left, right
64
+ "REGEXP_LIKE( #{left}, #{right} )"
65
+ end
66
+
67
+ def regexp_not left, right
68
+ "NOT #{regexp left, right}"
69
+ end
70
+ end
71
+ end
72
+
73
+ That's it (and the 'NOT' is only necessary if you use meta_where or something
74
+ that enables negative Arel statements to be generated). You just have to make
75
+ sure it is named correctly (needs to be the same as your actual adapter, so if
76
+ you're using `SomeOtherOracleAdapter` then you need to name your module that
77
+ too).
78
+
79
+ Wherex will pick this module up, and add it into the right place. Just see
80
+ `lib/wherex/adapters.rb` for more examples.
81
+
82
+ ## Testing
83
+
84
+ There are test suites for all three database engines that ship with Rails, the
85
+ default is SQLite and requires no preconfiguration. So you can just clone the
86
+ repo and run `rake` and it will run the tests against SQLite
87
+
88
+ If you want to run the tests against MySQL or PostgreSQL then you will first
89
+ need to create a `wherex_test` database in your local machine. Then you will
90
+ need to provide the `RAILS_DB` environment variable to rake, eg:
91
+
92
+ RAILS_DB=mysql rake
93
+
94
+ …or…
95
+
96
+ RAILS_DB=postgres rake
97
+
98
+ These will use a default user of `root` for MySQL and `postgres` for PostgreSQL.
99
+ If you want to use different usernames or passwords then take a look in the
100
+ `config/database.yml` file and either provide the appropriate environment
101
+ variables, or edit the file itself.
102
+
103
+ ## Copyright
104
+
105
+ Copyright © 2011 Jason King. See LICENSE.txt for further details.
106
+
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ spec = Gem::Specification.load( File.join( File.dirname(__FILE__), 'wherex.gemspec' ) )
13
+
14
+ $:.push File.expand_path("../lib", __FILE__)
15
+ require "wherex/version"
16
+
17
+ require 'rake/testtask'
18
+ Rake::TestTask.new(:test) do |test|
19
+ test.libs << 'lib' << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+
24
+ require 'rcov/rcovtask'
25
+ Rcov::RcovTask.new do |test|
26
+ test.libs << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ task :default => :test
@@ -0,0 +1,35 @@
1
+ module Wherex
2
+ module AbstractAdapter
3
+
4
+ def regexp_operator
5
+ "REGEXP"
6
+ end
7
+
8
+ def regexp_not_operator
9
+ "NOT #{regexp_operator}"
10
+ end
11
+
12
+ def regexp left, right
13
+ "#{left} #{regexp_operator} #{right}"
14
+ end
15
+
16
+ def regexp_not left, right
17
+ "#{left} #{regexp_not_operator} #{right}"
18
+ end
19
+
20
+ def regexp_quote str
21
+ quote str
22
+ end
23
+ end
24
+
25
+ module PostgreSQLAdapter
26
+
27
+ def regexp_operator
28
+ "~"
29
+ end
30
+
31
+ def regexp_not_operator
32
+ "!~"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Wherex
2
+ module ActiveRecord
3
+ def self.extended base
4
+ base.class_eval do
5
+ class << self
6
+ alias_method_chain :establish_connection, :wherex
7
+ end
8
+ end
9
+ end
10
+
11
+ def establish_connection_with_wherex spec = nil
12
+ establish_connection_without_wherex spec
13
+
14
+ require 'wherex/adapters'
15
+ ::ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, AbstractAdapter
16
+
17
+ adapter = ::ActiveRecord::Base.connection.class
18
+ my_adapter_name = adapter.to_s.demodulize
19
+
20
+ begin
21
+ my_adapter = "Wherex::#{my_adapter_name}".constantize
22
+ adapter.send :include, my_adapter
23
+ rescue NameError => e
24
+ end
25
+
26
+ if defined? ::ActiveRecord::ConnectionAdapters::SQLiteAdapter
27
+ ::ActiveRecord::Base.connection.raw_connection.create_function( "regexp", 2 ) do |context, pattern, string|
28
+ if string.present?
29
+ context.result = 1 if string.match pattern
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Wherex
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,33 @@
1
+ module Wherex
2
+ module Visitor
3
+
4
+ def self.included base
5
+ base.class_eval do
6
+ alias_method_chain :visit_Arel_Nodes_Equality, :wherex
7
+ alias_method_chain :visit_Arel_Nodes_NotEqual, :wherex
8
+ end
9
+ end
10
+
11
+ def visit_Arel_Nodes_Equality_with_wherex o
12
+ right = o.right
13
+ if right.present? and right.is_a? Regexp
14
+ @connection.regexp visit(o.left), visit(right)
15
+ else
16
+ visit_Arel_Nodes_Equality_without_wherex o
17
+ end
18
+ end
19
+
20
+ def visit_Arel_Nodes_NotEqual_with_wherex o
21
+ right = o.right
22
+ if right.present? and right.is_a? Regexp
23
+ @connection.regexp_not visit(o.left), visit(right)
24
+ else
25
+ visit_Arel_Nodes_NotEqual_without_wherex o
26
+ end
27
+ end
28
+
29
+ def visit_Regexp o; @connection.regexp_quote(o.source) end
30
+
31
+ end
32
+ end
33
+
data/lib/wherex.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'arel'
2
+ require 'active_record'
3
+ require 'active_support/core_ext'
4
+
5
+ require 'wherex/visitor'
6
+ require 'wherex/connection'
7
+ module Wherex
8
+ ::Arel::Visitors::ToSql.send :include, Visitor
9
+ ::ActiveRecord::Base.send :extend, ActiveRecord
10
+ end
@@ -0,0 +1,2 @@
1
+ class Product < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Student < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,22 @@
1
+ test:
2
+ postgres:
3
+ adapter: postgresql
4
+ encoding: unicode
5
+ database: wherex_test
6
+ pool: 5
7
+ username: <%= ENV['POSTGRESQL_USERNAME'] || 'postgres' %>
8
+ password: <%= ENV['POSTGRESQL_PASSWORD'] || '' %>
9
+ mysql:
10
+ adapter: mysql2
11
+ encoding: utf8
12
+ reconnect: false
13
+ database: wherex_test
14
+ pool: 5
15
+ username: <%= ENV['MYSQL_USERNAME'] || 'root' %>
16
+ password: <%= ENV['MYSQL_PASSWORD'] || '' %>
17
+ socket: <%= ENV['MYSQL_SOCKET'] || '/tmp/mysql.sock' %>
18
+ sqlite:
19
+ adapter: sqlite3
20
+ database: test/db/test.sqlite3
21
+ pool: 5
22
+ timeout: 5000
Binary file
@@ -0,0 +1,15 @@
1
+ ---
2
+ one:
3
+ code: N123-4394-2344
4
+
5
+ two:
6
+ code: P123-4394-2234
7
+
8
+ three:
9
+ code: N343-899434-32434
10
+
11
+ four:
12
+ code: 1N34-4543-3434
13
+
14
+ five:
15
+ code: N2343-32343-4444
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "users", :force => true do |t|
4
+ t.string "zipcode"
5
+ t.timestamps
6
+ end
7
+
8
+ create_table "students", :force => true do |t|
9
+ t.string "name"
10
+ t.timestamps
11
+ end
12
+
13
+ create_table "products", :force => true do |t|
14
+ t.string "code"
15
+ t.timestamps
16
+ end
17
+ end
18
+
19
+
@@ -0,0 +1,12 @@
1
+ ---
2
+ one:
3
+ name: Jimbo Jones
4
+
5
+ two:
6
+ name: Jóse Guervo
7
+
8
+ three:
9
+ name: Henri D'Havelan
10
+
11
+ four:
12
+ name: foo_bar
@@ -0,0 +1,27 @@
1
+ ---
2
+ one:
3
+ zipcode: 92345
4
+
5
+ two:
6
+ zipcode: 93456
7
+
8
+ three:
9
+ zipcode: 94567
10
+
11
+ four:
12
+ zipcode: 95678
13
+
14
+ five:
15
+ zipcode: 95abc
16
+
17
+ six:
18
+ zipcode: 9512
19
+
20
+ seven:
21
+ zipcode: 96789
22
+
23
+ eight:
24
+ zipcode: 194123
25
+
26
+ nine:
27
+ zipcode: 12345
data/test/helper.rb ADDED
@@ -0,0 +1,44 @@
1
+ ENV['RAILS_DB'] = ( ENV['RAILS_DB'] || 'sqlite' ).downcase
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ Bundler.setup
6
+ require 'test/unit'
7
+ begin; require 'redgreen'; rescue LoadError; end
8
+ begin; require 'turn'; rescue LoadError; end
9
+
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
11
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
12
+
13
+ require 'wherex'
14
+ require 'active_record/fixtures'
15
+
16
+ ROOT_PATH = File.expand_path( File.join( File.dirname(__FILE__) ))
17
+ FIXTURES_PATH = File.join(File.dirname(__FILE__), 'fixtures')
18
+
19
+ config = YAML::load(ERB.new(IO.read(File.join( ROOT_PATH, 'config', 'database.yml'))).result)['test'][ENV['RAILS_DB']]
20
+
21
+ ActiveRecord::Base.establish_connection config
22
+
23
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
24
+ dep.autoload_paths.unshift File.join( ROOT_PATH, 'app/models' )
25
+
26
+ dep.autoload_paths.unshift FIXTURES_PATH
27
+
28
+ ActiveRecord::Base.silence do
29
+ ActiveRecord::Migration.verbose = false
30
+ load File.join(FIXTURES_PATH, 'schema.rb')
31
+ end
32
+
33
+ Fixtures.create_fixtures(FIXTURES_PATH, ActiveRecord::Base.connection.tables)
34
+
35
+ class Test::Unit::TestCase
36
+ def method_missing method, *args
37
+ candidate = method.to_s.classify
38
+ begin
39
+ candidate.constantize.find( Fixtures.identify args[0] )
40
+ rescue NameError
41
+ raise NameError.new "undefined local variable or method `#{method}`"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ class TestWherex < Test::Unit::TestCase
4
+ def test_user_example
5
+ assert u = User.where( :zipcode => /^9[345][0-9]{3}$/ )
6
+ assert_equal 3, u.count
7
+ [:two, :three, :four ].each do |id|
8
+ assert u.include?( users(id) )
9
+ end
10
+ end
11
+
12
+ def test_product_example
13
+ assert p = Product.find_by_code( /^[NRW][^-]+-[456]/ )
14
+ assert_equal products(:one), p
15
+
16
+ assert p = Product.find_all_by_code( /^[NRW][^-]+-[456]/ )
17
+ assert_equal 1, p.count
18
+ assert_equal products(:one), p.first
19
+ end
20
+
21
+ def test_student_example
22
+ assert s = Student.all( :conditions => { :name => /[^a-zA-Z ]/ } )
23
+ assert_equal 3, s.count
24
+ [ :two, :three, :four ].each do |id|
25
+ assert s.include?( students(id) )
26
+ end
27
+ end
28
+ end
data/wherex.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "wherex/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "wherex"
6
+ s.version = Wherex::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Jason King"]
9
+ s.email = ["jk@handle.it"]
10
+ s.homepage = "http://github.com/smathy/wherex"
11
+ s.license = "MIT"
12
+ s.summary = %q{Regexp for ActiveRecord finders}
13
+ s.description = %q{This gem allows you to pass a Regexp as the value for any finder in ActiveRecord - Rails3 only}
14
+
15
+ s.rubyforge_project = "wherex"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'arel', '~> 2.0'
23
+ s.add_dependency 'activerecord', '~> 3.0'
24
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wherex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Jason King
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-11 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: arel
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 2
31
+ - 0
32
+ version: "2.0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 3
46
+ - 0
47
+ version: "3.0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: This gem allows you to pass a Regexp as the value for any finder in ActiveRecord - Rails3 only
51
+ email:
52
+ - jk@handle.it
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - LICENSE.txt
63
+ - README.md
64
+ - Rakefile
65
+ - lib/wherex.rb
66
+ - lib/wherex/adapters.rb
67
+ - lib/wherex/connection.rb
68
+ - lib/wherex/version.rb
69
+ - lib/wherex/visitor.rb
70
+ - test/app/models/product.rb
71
+ - test/app/models/student.rb
72
+ - test/app/models/user.rb
73
+ - test/config/database.yml
74
+ - test/db/test.sqlite3
75
+ - test/fixtures/products.yml
76
+ - test/fixtures/schema.rb
77
+ - test/fixtures/students.yml
78
+ - test/fixtures/users.yml
79
+ - test/helper.rb
80
+ - test/test_wherex.rb
81
+ - wherex.gemspec
82
+ homepage: http://github.com/smathy/wherex
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project: wherex
111
+ rubygems_version: 1.7.2
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Regexp for ActiveRecord finders
115
+ test_files:
116
+ - test/app/models/product.rb
117
+ - test/app/models/student.rb
118
+ - test/app/models/user.rb
119
+ - test/config/database.yml
120
+ - test/db/test.sqlite3
121
+ - test/fixtures/products.yml
122
+ - test/fixtures/schema.rb
123
+ - test/fixtures/students.yml
124
+ - test/fixtures/users.yml
125
+ - test/helper.rb
126
+ - test/test_wherex.rb