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