findable_by 0.1.0
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/README.markdown +46 -0
- data/init.rb +1 -0
- data/lib/findable_by/finders/between_finder.rb +5 -0
- data/lib/findable_by/finders/equals_finder.rb +5 -0
- data/lib/findable_by/finders/like_finder.rb +5 -0
- data/lib/findable_by/finders/proc_finder.rb +11 -0
- data/lib/findable_by/version.rb +3 -0
- data/lib/findable_by.rb +92 -0
- data/test/connection/native_sqlite3/in_memory_connection.rb +9 -0
- data/test/findable_by_test.rb +62 -0
- data/test/schema/schema.rb +13 -0
- data/test/support/models.rb +20 -0
- data/test/test_helper.rb +16 -0
- metadata +83 -0
data/README.markdown
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
findable_by
|
2
|
+
=
|
3
|
+
|
4
|
+
findable_by is a simple Rails 3 plugin to improve a way how you search for records. You can pass the search form parameters directly to the find_with method in your relation, and the plugin will take care to build a correct query in a safer way.
|
5
|
+
|
6
|
+
See below how it's works.
|
7
|
+
|
8
|
+
Install:
|
9
|
+
-
|
10
|
+
|
11
|
+
In your Gemfile:
|
12
|
+
|
13
|
+
gem "findable_by"
|
14
|
+
|
15
|
+
Usage
|
16
|
+
-
|
17
|
+
|
18
|
+
class Person < ActiveRecord::Base
|
19
|
+
scope :gender, proc { |value| where(:gender => value) }
|
20
|
+
|
21
|
+
findable_by :first_name, :using => :like
|
22
|
+
findable_by :last_name, :using => UpcaseFinder
|
23
|
+
findable_by :primary_contact_id, :using => :equals
|
24
|
+
findable_by :number1_fan, :using => proc { |attribute, value| where("1=1") }
|
25
|
+
findable_by :gender, :using => proc { |attribute, value| gender(value) }
|
26
|
+
findable_by :age, :using => :between, :with => [ :min, :max ]
|
27
|
+
findable_by :birth_date, :using => :between, :with => [ :min, :max ]
|
28
|
+
end
|
29
|
+
|
30
|
+
And now you can search:
|
31
|
+
|
32
|
+
relation = Person.scoped
|
33
|
+
relation = relation.find_with(params[:search])
|
34
|
+
relation.all
|
35
|
+
|
36
|
+
TODO
|
37
|
+
-
|
38
|
+
|
39
|
+
README and some documentation.
|
40
|
+
|
41
|
+
Bugs and Feedback
|
42
|
+
-
|
43
|
+
|
44
|
+
http://github.com/thiagomoretto/findable_by/issues
|
45
|
+
|
46
|
+
MIT License. Copyright 2010.
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "findable_by"
|
data/lib/findable_by.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module FindableBy
|
2
|
+
module Relation
|
3
|
+
def find_with(params)
|
4
|
+
relation = self.dup
|
5
|
+
params.each do |attribute, value|
|
6
|
+
finder = self._finders[attribute]
|
7
|
+
|
8
|
+
if not finder.blank?
|
9
|
+
if not finder.options[:with].blank?
|
10
|
+
# value is replaced in this case.
|
11
|
+
value = _fb_override_value(attribute, finder.options[:with], params)
|
12
|
+
end
|
13
|
+
|
14
|
+
if _fb_is_value_ok(value)
|
15
|
+
relation = finder.send(:build_condition, relation, attribute, value, params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end if not params.blank?
|
19
|
+
|
20
|
+
relation
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def _fb_is_value_ok(value)
|
25
|
+
not value.blank? or (value.is_a?(Hash) and value.all?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def _fb_override_value(attribute, with_opts, params)
|
29
|
+
case with_opts
|
30
|
+
when Symbol
|
31
|
+
raise NotImplementedError
|
32
|
+
when Array
|
33
|
+
value = with_opts.collect{ |wp| params[attribute][wp] }
|
34
|
+
end
|
35
|
+
value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ActsAsFindable
|
40
|
+
extend ActiveSupport::Concern
|
41
|
+
|
42
|
+
included do
|
43
|
+
class_attribute :_finders
|
44
|
+
self._finders = HashWithIndifferentAccess.new{ |h,k| h[k] = [] }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
module ClassMethods
|
49
|
+
def findable_by(*attr_names)
|
50
|
+
options = attr_names.extract_options!
|
51
|
+
options.merge!(:attributes => attr_names.flatten)
|
52
|
+
options[:using] = :equals if not options.key?(:using)
|
53
|
+
options[:attributes].each do |attribute|
|
54
|
+
using_what = options[:using]
|
55
|
+
|
56
|
+
if using_what.is_a?(Class)
|
57
|
+
self._finders[attribute] = using_what.new(options)
|
58
|
+
elsif using_what.is_a?(Proc)
|
59
|
+
self._finders[attribute] = ProcFinder.new(options, using_what)
|
60
|
+
else
|
61
|
+
self._finders[attribute] = Kernel.const_get("#{options[:using]}_finder".classify).new(options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def inherited(base)
|
67
|
+
dup = _finders.dup
|
68
|
+
base._finders = dup.each{ |k,v| dup[k] = v.dup }
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Finder
|
76
|
+
attr_accessor :options
|
77
|
+
def initialize(options)
|
78
|
+
self.options = options
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_condition(relation, attribute, value, params)
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Dir[File.dirname(__FILE__) + "/findable_by/finders/*.rb"].sort.each do |path|
|
87
|
+
filename = File.basename(path)
|
88
|
+
require "findable_by/finders/#{filename}"
|
89
|
+
end
|
90
|
+
|
91
|
+
ActiveRecord::Base.send(:include, FindableBy::ActsAsFindable)
|
92
|
+
ActiveRecord::Relation.send(:include, FindableBy::Relation)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
print "Using native SQLite3\n"
|
2
|
+
require 'logger'
|
3
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
4
|
+
|
5
|
+
class SqliteError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
9
|
+
load("schema/schema.rb")
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'support/models'
|
4
|
+
|
5
|
+
class FindableByTest < ActiveRecord::TestCase
|
6
|
+
|
7
|
+
test "should generate a SQL to find a person by first name using like" do
|
8
|
+
with_scoped(Person) do |person|
|
9
|
+
assert_not_nil person.find_with(:first_name => "OMG!").to_sql
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
test "should generate a SQL to find a person using a proc" do
|
14
|
+
with_scoped(Person) do |person|
|
15
|
+
assert_not_nil person.find_with(:number1_fan => "Don't care").to_sql
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
test "should generate a SQL to find a person using a proc calling a scope" do
|
20
|
+
with_scoped(Person) do |person|
|
21
|
+
sql = person.find_with("first_name" => "OMG!", :gender => 'M').to_sql
|
22
|
+
assert_not_nil sql =~ /first_name/ and sql =~ /gender/
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
test "should generate a SQL to find a person calling 'find_with' chained" do
|
27
|
+
with_scoped(Person) do |person|
|
28
|
+
assert_not_nil person.find_with(:first_name => "OMG!").find_with(:gender => 'F').to_sql
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
test "should generate a SQL to find a person by primary_contact_id using equals" do
|
33
|
+
with_scoped(Person) do |person|
|
34
|
+
assert_not_nil person.find_with(:primary_contact_id => 1).to_sql
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test "should generate a SQL to find a person using a custom finder" do
|
39
|
+
with_scoped(Person) do |person|
|
40
|
+
assert person.find_with(:last_name => "omg").to_sql =~ /OMG/
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
test "should generate a SQL to find people by age range" do
|
45
|
+
with_scoped(Person) do |person|
|
46
|
+
assert person.find_with(:age => { :min => 20, :max => 45 }).to_sql =~ /BETWEEN 20 AND 45/
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
test "should generate a SQL to find people by birth date range" do
|
51
|
+
with_scoped(Person) do |person|
|
52
|
+
params, params[:birth_date] = {}, {}
|
53
|
+
params[:birth_date][:min], params[:birth_date][:max] = Time.now - 1.day, Time.now + 1.day
|
54
|
+
assert person.find_with(params).to_sql =~ /BETWEEN/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
private
|
58
|
+
def with_scoped(model, &block)
|
59
|
+
block.call(model.send(:scoped))
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
|
3
|
+
create_table :people, :force => true do |t|
|
4
|
+
t.string :first_name, :null => false
|
5
|
+
t.string :last_name
|
6
|
+
t.references :primary_contact
|
7
|
+
t.string :gender, :limit => 1
|
8
|
+
t.references :number1_fan
|
9
|
+
t.integer :age, :limit => 3
|
10
|
+
t.integer :lock_version, :null => false, :default => 0
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class UpcaseFinder < Finder
|
2
|
+
def build_condition(relation, attribute, value, params)
|
3
|
+
relation.where(attribute => value.upcase!)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Person < ActiveRecord::Base
|
8
|
+
scope :gender, proc { |value| where(:gender => 'F') }
|
9
|
+
|
10
|
+
findable_by :first_name, :using => :like
|
11
|
+
findable_by :last_name, :using => UpcaseFinder
|
12
|
+
findable_by :primary_contact_id, :using => :equals
|
13
|
+
findable_by :number1_fan, :using => proc { |attribute, value| where("1=1") }
|
14
|
+
findable_by :gender, :using => proc { |attribute, value| gender(value) }
|
15
|
+
findable_by :age, :using => :between, :with => [ :min, :max ]
|
16
|
+
findable_by :birth_date, :using => :between, :with => [ :min, :max ]
|
17
|
+
|
18
|
+
# TODO: findable_by :gender, :through => :gender # uses scope :gender
|
19
|
+
# TODO: findable_by :gender # uses (:using => :equals)
|
20
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'mocha'
|
8
|
+
|
9
|
+
require 'active_model'
|
10
|
+
require 'active_record'
|
11
|
+
require 'action_controller'
|
12
|
+
|
13
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
14
|
+
require 'findable_by'
|
15
|
+
require 'connection/native_sqlite3/in_memory_connection'
|
16
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: findable_by
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Thiago Moretto
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-12 00:00:00 -03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Even easier to find records!
|
23
|
+
email: thiago@moretto.eng.br
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.markdown
|
30
|
+
files:
|
31
|
+
- init.rb
|
32
|
+
- lib/findable_by.rb
|
33
|
+
- lib/findable_by/finders/between_finder.rb
|
34
|
+
- lib/findable_by/finders/equals_finder.rb
|
35
|
+
- lib/findable_by/finders/like_finder.rb
|
36
|
+
- lib/findable_by/finders/proc_finder.rb
|
37
|
+
- lib/findable_by/version.rb
|
38
|
+
- README.markdown
|
39
|
+
- test/connection/native_sqlite3/in_memory_connection.rb
|
40
|
+
- test/findable_by_test.rb
|
41
|
+
- test/schema/schema.rb
|
42
|
+
- test/support/models.rb
|
43
|
+
- test/test_helper.rb
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/thiagomoretto/findable_by
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.3.7
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Improving a way how you find records!
|
78
|
+
test_files:
|
79
|
+
- test/connection/native_sqlite3/in_memory_connection.rb
|
80
|
+
- test/findable_by_test.rb
|
81
|
+
- test/schema/schema.rb
|
82
|
+
- test/support/models.rb
|
83
|
+
- test/test_helper.rb
|