scoped-search 0.1.1
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/.gitignore +1 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.textile +125 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/init.rb +2 -0
- data/lib/scoped_search.rb +103 -0
- data/spec/scoped_search_spec.rb +69 -0
- data/spec/spec_helper.rb +31 -0
- metadata +93 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.bundle
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Novagile, Written by Nicolas Blanco.
|
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.textile
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
h1. ScopedSearch
|
2
|
+
|
3
|
+
ScopedSearch is a plugin to easily create search forms and do column ordering.
|
4
|
+
It is written specifically for Rails 3 and is compatible with both ActiveRecord and "Mongoid":http://www.mongoid.org.
|
5
|
+
|
6
|
+
ScopeSearch is Copyright (c) 2010 Novagile, written by "Nicolas Blanco":http://github.com/slainer68
|
7
|
+
|
8
|
+
h2. Philosophy
|
9
|
+
|
10
|
+
I've begun writing this plugin because I really like "Searchlogic":http://github.com/binarylogic/searchlogic from "Ben Johnson":http://www.binarylogic.com.
|
11
|
+
I was using it in all my Rails 2 Active Record projects.
|
12
|
+
|
13
|
+
Searchlogic has a feature that dynamically adds a lot of scopes in your models. But a feature from Searchlogic I really like is the ability to create an object
|
14
|
+
that binds to your models scopes and being able to use it directly in a form. You can add many fields in your search forms, and the controller stays
|
15
|
+
the same. I really wanted the same behaviour in Rails 3 and compatible with both ActiveRecord and Mongoid.
|
16
|
+
|
17
|
+
h2. Installation
|
18
|
+
|
19
|
+
For now, ScopedSearch is under development.
|
20
|
+
You may use it as plugin or as a gem.
|
21
|
+
|
22
|
+
**Gem**
|
23
|
+
|
24
|
+
Edit your Gemfile and add :
|
25
|
+
|
26
|
+
<pre>
|
27
|
+
gem 'scoped-search'
|
28
|
+
</pre>
|
29
|
+
|
30
|
+
**Plugin**
|
31
|
+
|
32
|
+
<pre>
|
33
|
+
rails plugin install git://github.com/novagile/scoped-search.git
|
34
|
+
</pre>
|
35
|
+
|
36
|
+
ScopedSearch does not dynamically include itself in ActiveRecord or Mongoid, you have to include it in your models where you want to use it.
|
37
|
+
|
38
|
+
Simply include the ScopedSearch::Model module in your models like this...
|
39
|
+
|
40
|
+
<pre>
|
41
|
+
class Post < ActiveRecord::Base
|
42
|
+
include ScopedSearch::Model
|
43
|
+
|
44
|
+
scope :retrieve, lambda { |q| where("title like ?", "%#{q}%") }
|
45
|
+
scope :state_equals, lambda { |state| where( {:state => state }) }
|
46
|
+
scope :published, where(:published => true)
|
47
|
+
...
|
48
|
+
</pre>
|
49
|
+
|
50
|
+
In your ApplicationHelper, include the ScopedSearch::Helpers module :
|
51
|
+
|
52
|
+
<pre>
|
53
|
+
module ApplicationHelper
|
54
|
+
include ScopedSearch::Helpers
|
55
|
+
end
|
56
|
+
</pre>
|
57
|
+
|
58
|
+
h2. Console testing
|
59
|
+
|
60
|
+
You can test the search object in a console!
|
61
|
+
|
62
|
+
<pre>
|
63
|
+
> search = Post.scoped_search
|
64
|
+
=> #<ScopedSearch::Base...>
|
65
|
+
> search.count
|
66
|
+
=> 4
|
67
|
+
> search.all.map(&:body)
|
68
|
+
=> ["test", "lalala", "foo", "bar"]
|
69
|
+
> search.retrieve = "foo"
|
70
|
+
=> "foo"
|
71
|
+
> search.count
|
72
|
+
=> 1
|
73
|
+
> search.all
|
74
|
+
=> [#<Post id: 3, title: nil, body: "foo"...]
|
75
|
+
</pre>
|
76
|
+
|
77
|
+
h2. Usage
|
78
|
+
|
79
|
+
Then in your controller you can do like this :
|
80
|
+
|
81
|
+
<pre>
|
82
|
+
class PostsController < ApplicationController
|
83
|
+
def index
|
84
|
+
@search = Post.scoped_search(params[:search])
|
85
|
+
@posts = @search.all # or @search.paginate(...), or you can even continue the scope chain ! Just add your scopes like this : @search.build_relation.other_scope.other_other_scope... :)
|
86
|
+
...
|
87
|
+
</pre>
|
88
|
+
|
89
|
+
In your view, you can create a form that takes your search object as parameter, like this :
|
90
|
+
|
91
|
+
<pre>
|
92
|
+
<%= form_for @search do |f| %>
|
93
|
+
<%= f.text_field :retrieve %>
|
94
|
+
<%= f.select :state_equals, ["pending", "accepted", "deleted"] %>
|
95
|
+
<%= submit_tag "Search" %>
|
96
|
+
<% end %>
|
97
|
+
</pre>
|
98
|
+
|
99
|
+
h2. Column ordering
|
100
|
+
|
101
|
+
You want to get column ordering for free? Sure!
|
102
|
+
In your model, use the scoped_order method like this :
|
103
|
+
|
104
|
+
<pre>
|
105
|
+
class Post < ActiveRecord::Base
|
106
|
+
...
|
107
|
+
scoped_order :title, created_at, :updated_at # , ...
|
108
|
+
...
|
109
|
+
</pre>
|
110
|
+
|
111
|
+
It will add two scopes for each column, named "ascend_by_column_name" and "descend_by_column_name" (like Searchlogic).
|
112
|
+
|
113
|
+
Then in your views, you may use the order_for_scoped_search view helper like this :
|
114
|
+
|
115
|
+
<pre>
|
116
|
+
<table>
|
117
|
+
<tr>
|
118
|
+
<th><%= link_to "Title", order_for_scoped_search(:title) %></th>
|
119
|
+
...
|
120
|
+
</pre>
|
121
|
+
|
122
|
+
h2. Notes
|
123
|
+
|
124
|
+
This plugin is new and under development, patches and contributions are welcome! Please fork and make pull requests, thanks!
|
125
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/rdoctask"
|
3
|
+
require "rspec"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.name = "scoped-search"
|
10
|
+
gem.summary = %Q{Easily implement search forms and column ordering based on your models scopes}
|
11
|
+
gem.description = %Q{Easily implement search forms and column ordering based on your models scopes. For Rails 3, compatible with ActiveRecord and Mongoid.}
|
12
|
+
gem.email = "slainer68@gmail.com"
|
13
|
+
gem.homepage = "http://github.com/novagile/scoped-search"
|
14
|
+
gem.authors = ["slainer68"]
|
15
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
Rspec::Core::RakeTask.new(:spec) do |spec|
|
24
|
+
spec.pattern = "spec/**/*_spec.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
29
|
+
|
30
|
+
rdoc.rdoc_dir = 'rdoc'
|
31
|
+
rdoc.title = "scoped_search #{version}"
|
32
|
+
rdoc.rdoc_files.include('README*')
|
33
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
34
|
+
end
|
35
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/init.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# ScopedSearch, Copyright (c) 2010 Novagile.
|
2
|
+
# Written by Nicolas Blanco.
|
3
|
+
# Licensed under MIT license.
|
4
|
+
#
|
5
|
+
class ScopedSearch
|
6
|
+
class Base
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
|
9
|
+
SINGLE_SCOPES_VALUES = %w(true 1)
|
10
|
+
|
11
|
+
attr_reader :attributes, :model_class, :attributes_merged
|
12
|
+
|
13
|
+
def initialize(klass, options)
|
14
|
+
@model_class = klass
|
15
|
+
@attributes = options
|
16
|
+
@attributes_merged = @attributes.reverse_merge(klass.scopes.keys.inject({}) { |m,o| m[o] = nil; m; })
|
17
|
+
|
18
|
+
@attributes_merged.each do |attribute, value|
|
19
|
+
class_eval <<-RUBY
|
20
|
+
def #{attribute}
|
21
|
+
@attributes[:#{attribute}]
|
22
|
+
end
|
23
|
+
|
24
|
+
def #{attribute}=(val)
|
25
|
+
@attributes[:#{attribute}] = val
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_relation
|
32
|
+
return model_class if attributes.empty?
|
33
|
+
attributes.reject { |k,v| v.blank? }.inject(model_class) do |s, k|
|
34
|
+
if model_class.scopes.keys.include?(k.first.to_sym)
|
35
|
+
k.size == 2 && SINGLE_SCOPES_VALUES.include?(k.last.to_s) ? s.send(k.first) : s.send(*k)
|
36
|
+
else
|
37
|
+
s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_key; nil; end
|
43
|
+
|
44
|
+
def method_missing(method_name, *args)
|
45
|
+
build_relation.send(method_name, *args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Helpers
|
50
|
+
extend ActiveSupport::Concern
|
51
|
+
|
52
|
+
def form_for(record, *args, &block)
|
53
|
+
if record.is_a?(ScopedSearch::Base)
|
54
|
+
options = args.extract_options!
|
55
|
+
options.symbolize_keys!
|
56
|
+
|
57
|
+
options.reverse_merge!({ :url => polymorphic_path(record.model_class), :as => :search })
|
58
|
+
options[:html] ||= {}
|
59
|
+
options[:html].reverse_merge!({ :method => :get })
|
60
|
+
args << options
|
61
|
+
end
|
62
|
+
|
63
|
+
super(record, *args, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# TODO :
|
67
|
+
# refactor this ?
|
68
|
+
def order_for_scoped_search(column, search_param = :search)
|
69
|
+
search_order = params[search_param].present? && params[search_param]["ascend_by_#{column}"].present? ? { "descend_by_#{column}" => true } : { "ascend_by_#{column}" => true }
|
70
|
+
|
71
|
+
params[search_param] ||= {}
|
72
|
+
search_without_order = params[search_param].clone
|
73
|
+
search_without_order.delete_if { |k,v| k.to_s.starts_with?("ascend_by") || k.to_s.starts_with?("descend_by") }
|
74
|
+
|
75
|
+
params.merge(search_param => search_without_order.merge(search_order))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
module Model
|
80
|
+
extend ActiveSupport::Concern
|
81
|
+
|
82
|
+
module ClassMethods
|
83
|
+
def scoped_search(options={})
|
84
|
+
ScopedSearch::Base.new(self, options.present? ? options : {})
|
85
|
+
end
|
86
|
+
|
87
|
+
def scoped_order(*columns_names)
|
88
|
+
if defined?(Mongoid) && self.include?(Mongoid::Document)
|
89
|
+
columns_names.each do |column_name|
|
90
|
+
scope :"ascend_by_#{column_name}", order_by([column_name.to_sym, :desc])
|
91
|
+
scope :"descend_by_#{column_name}", order_by([column_name.to_sym, :asc])
|
92
|
+
end
|
93
|
+
else
|
94
|
+
columns_names.each do |column_name|
|
95
|
+
scope :"ascend_by_#{column_name}", order("#{column_name} asc")
|
96
|
+
scope :"descend_by_#{column_name}", order("#{column_name} desc")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
%w(blahblah foo bar pipo waouhhh pipomolo).each do |title|
|
4
|
+
Post.create(:title => title, :body => "blah", :published => title.include?("pipo"))
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'ScopedSearch' do
|
8
|
+
it 'should respond to scoped_search and return a ScopedSearch::Base object' do
|
9
|
+
Post.should respond_to(:scoped_search)
|
10
|
+
Post.scoped_search.should be_a(ScopedSearch::Base)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have the same count when initializing an empty search' do
|
14
|
+
Post.count.should == 6
|
15
|
+
|
16
|
+
@search = Post.scoped_search
|
17
|
+
@search.count.should == 6
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'initialization' do
|
21
|
+
it 'should be possible to initialize without a hash and change attributes later' do
|
22
|
+
@search = Post.scoped_search
|
23
|
+
@search.attributes.should == {}
|
24
|
+
|
25
|
+
@search.retrieve = "pipo"
|
26
|
+
@search.published = true
|
27
|
+
@search.attributes.should have(2).elements
|
28
|
+
@search.attributes[:retrieve].should == "pipo"
|
29
|
+
@search.attributes[:published].should == true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be possible to initialize with a hash' do
|
33
|
+
@search = Post.scoped_search({ :retrieve => "pipo", :published => true })
|
34
|
+
@search.attributes.should have(2).elements
|
35
|
+
|
36
|
+
@search.retrieve.should == "pipo"
|
37
|
+
@search.published.should == true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'searching' do
|
42
|
+
it 'should be possible to search with one parameter' do
|
43
|
+
@search = Post.scoped_search({ :retrieve => "foo" })
|
44
|
+
@search.retrieve.should == "foo"
|
45
|
+
@search.count.should == 1
|
46
|
+
|
47
|
+
@search.all.should have(1).element
|
48
|
+
@search.retrieve = nil
|
49
|
+
@search.count.should == 6
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be possible to search with multiple parameters' do
|
53
|
+
@search = Post.scoped_search({ :retrieve => "foo", :published => true })
|
54
|
+
@search.retrieve.should == "foo"
|
55
|
+
@search.published.should == true
|
56
|
+
@search.count.should == 0
|
57
|
+
|
58
|
+
@search.all.should have(0).element
|
59
|
+
@search.retrieve = nil
|
60
|
+
@search.count.should == 2
|
61
|
+
@search.all.should have(2).elements
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should be possible to build the relation' do
|
66
|
+
@search = Post.scoped_search({ :retrieve => "foo" })
|
67
|
+
@search.build_relation.should be_a(ActiveRecord::Relation)
|
68
|
+
end
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ruby-debug'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
require "active_record"
|
5
|
+
|
6
|
+
ActiveRecord::Schema.verbose = false
|
7
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
8
|
+
|
9
|
+
ActiveRecord::Base.configurations = true
|
10
|
+
ActiveRecord::Schema.define(:version => 1) do
|
11
|
+
create_table :posts do |t|
|
12
|
+
t.string :title
|
13
|
+
t.text :body
|
14
|
+
t.boolean :published
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require File.dirname(__FILE__) + '/../lib/scoped_search'
|
21
|
+
|
22
|
+
class Post < ActiveRecord::Base
|
23
|
+
include ScopedSearch::Model
|
24
|
+
|
25
|
+
scope :published, where(:published => true)
|
26
|
+
scope :retrieve, lambda { |q| where("title like ?", "%#{q}%") }
|
27
|
+
end
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
config.mock_with :rspec
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scoped-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- slainer68
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-21 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Easily implement search forms and column ordering based on your models scopes. For Rails 3, compatible with ActiveRecord and Mongoid.
|
38
|
+
email: slainer68@gmail.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- LICENSE
|
45
|
+
- README.textile
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- LICENSE
|
50
|
+
- README.textile
|
51
|
+
- Rakefile
|
52
|
+
- VERSION
|
53
|
+
- init.rb
|
54
|
+
- lib/scoped_search.rb
|
55
|
+
- spec/scoped_search_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/novagile/scoped-search
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
hash: 3
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.7
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Easily implement search forms and column ordering based on your models scopes
|
91
|
+
test_files:
|
92
|
+
- spec/scoped_search_spec.rb
|
93
|
+
- spec/spec_helper.rb
|