scoped-search 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gem "rspec-rails", ">= 2.0.0.beta.17"
4
+ gem 'jeweler'
5
+ gem "rails", ">= 3.0.0.beta4"
6
+ gem "ruby-debug"
7
+
8
+ gem 'sqlite3-ruby', :require => 'sqlite3'
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,2 @@
1
+ require "scoped_search"
2
+
@@ -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
@@ -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