ransack 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,129 @@
1
+ # Ransack
2
+
3
+ Ransack is a rewrite of [MetaSearch](http://metautonomo.us/projects/metasearch). While it
4
+ supports many of the same features as MetaSearch, its underlying implementation differs
5
+ greatly from MetaSearch, and _backwards compatibility is not a design goal._
6
+
7
+ Ransack enables the creation of both simple and advanced search forms against your
8
+ application's models. If you're looking for something that simplifies query generation
9
+ at the model or controller layer, you're probably not looking for Ransack (or MetaSearch,
10
+ for that matter). Try [Squeel](http://metautonomo.us/projects/squeel) instead.
11
+
12
+ ## Getting started
13
+
14
+ In your Gemfile:
15
+
16
+ gem "ransack" # Last officially released gem
17
+ # gem "ransack", :git => "git://github.com/ernie/ransack.git" # Track git repo
18
+
19
+ If you'd like to add your own custom Ransack predicates:
20
+
21
+ Ransack.configure do |config|
22
+ config.add_predicate 'equals_diddly', # Name your predicate
23
+ # What non-compound ARel predicate will it use? (eq, matches, etc)
24
+ :arel_predicate => 'eq',
25
+ # Format incoming values as you see fit. (Default: Don't do formatting)
26
+ :formatter => proc {|v| "#{v}-diddly"},
27
+ # Validate a value. An "invalid" value won't be used in a search.
28
+ # Below is default.
29
+ :validator => proc {|v| v.present?},
30
+ # Should compounds be created? Will use the compound (any/all) version
31
+ # of the arel_predicate to create a corresponding any/all version of
32
+ # your predicate. (Default: true)
33
+ :compounds => true,
34
+ # Force a specific column type for type-casting of supplied values.
35
+ # (Default: use type from DB column)
36
+ :type => :string
37
+ end
38
+
39
+ ## Usage
40
+
41
+ Ransack can be used in one of two modes, simple or advanced.
42
+
43
+ ### Simple Mode
44
+
45
+ This mode works much like MetaSearch, for those of you who are familiar with it, and
46
+ requires very little setup effort.
47
+
48
+ If you're coming from MetaSearch, things to note:
49
+
50
+ 1. The default param key for search params is now `:q`, instead of `:search`. This is
51
+ primarily to shorten query strings, though advanced queries (below) will still
52
+ run afoul of URL length limits in most browsers and require a switch to HTTP
53
+ POST requests.
54
+ 2. `form_for` is now `search_form_for`, and validates that a Ransack::Search object
55
+ is passed to it.
56
+ 3. Common ActiveRecord::Relation methods are no longer delegated by the search object.
57
+ Instead, you will get your search results (an ActiveRecord::Relation in the case of
58
+ the ActiveRecord adapter) via a call to `Search#result`. If passed `:distinct => true`,
59
+ `result` will generate a `SELECT DISTINCT` to avoid returning duplicate rows, even if
60
+ conditions on a join would otherwise result in some.
61
+
62
+ In your controller:
63
+
64
+ def index
65
+ @q = Person.search(params[:q])
66
+ @people = @q.result(:distinct => true)
67
+ end
68
+
69
+ In your view:
70
+
71
+ <%= search_form_for @q do |f| %>
72
+ <%= f.label :name_cont %>
73
+ <%= f.text_field :name_cont %>
74
+ <%= f.label :articles_title_start %>
75
+ <%= f.text_field :articles_title_start %>
76
+ <%= f.submit %>
77
+ <% end %>
78
+
79
+ `cont` (contains) and `start` (starts with) are just two of the available search predicates.
80
+ See Constants for a full list.
81
+
82
+ ### Advanced Mode
83
+
84
+ "Advanced" searches (ab)use Rails' nested attributes functionality in order to generate
85
+ complex queries with nested AND/OR groupings, etc. This takes a bit more work but can
86
+ generate some pretty cool search interfaces that put a lot of power in the hands of
87
+ your users. A notable drawback with these searches is that the increased size of the
88
+ parameter string will typically force you to use the HTTP POST method instead of GET. :(
89
+
90
+ This means you'll need to tweak your routes...
91
+
92
+ resources :people do
93
+ collection do
94
+ match 'search' => 'people#search', :via => [:get, :post], :as => :search
95
+ end
96
+ end
97
+
98
+ ... and add another controller action ...
99
+
100
+ def search
101
+ index
102
+ render :index
103
+ end
104
+
105
+ ... and update your `search_form_for` line in the view ...
106
+
107
+ <%= search_form_for @q, :url => search_people_path,
108
+ :html => {:method => :post} do |f| %>
109
+
110
+ Once you've done so, you can make use of the helpers in Ransack::Helpers::FormBuilder to
111
+ construct much more complex search forms.
112
+
113
+ **more docs to come**
114
+
115
+ ## Contributions
116
+
117
+ If you'd like to support the continued development of Ransack, please consider
118
+ [making a donation](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=48Q9HY64L3TWA).
119
+
120
+ To support the project in other ways:
121
+
122
+ * Use Ransack in your apps, and let me know if you encounter anything that's broken or missing.
123
+ A failing spec is awesome. A pull request is even better!
124
+ * Spread the word on Twitter, Facebook, and elsewhere if Ransack's been useful to you. The more
125
+ people who are using the project, the quicker we can find and fix bugs!
126
+
127
+ ## Copyright
128
+
129
+ Copyright &copy; 2011 [Ernie Miller](http://twitter.com/erniemiller)
@@ -7,10 +7,6 @@ module Ransack
7
7
  mattr_accessor :predicates
8
8
  self.predicates = {}
9
9
 
10
- def predicate_keys
11
- predicates.keys.sort {|a,b| b.length <=> a.length}
12
- end
13
-
14
10
  def configure
15
11
  yield self
16
12
  end
@@ -111,7 +111,7 @@ module Ransack
111
111
 
112
112
  def predicate_select(options = {}, html_options = {})
113
113
  options[:compounds] = true if options[:compounds].nil?
114
- keys = options[:compounds] ? Ransack.predicate_keys : Ransack.predicate_keys.reject {|k| k.match(/_(any|all)$/)}
114
+ keys = options[:compounds] ? Predicate.names : Predicate.names.reject {|k| k.match(/_(any|all)$/)}
115
115
  if only = options[:only]
116
116
  if only.respond_to? :call
117
117
  keys = keys.select {|k| only.call(k)}
@@ -28,7 +28,7 @@ module Ransack
28
28
 
29
29
  def extract_attributes_and_predicate(key)
30
30
  str = key.dup
31
- name = Ransack.predicate_keys.detect {|p| str.sub!(/_#{p}$/, '')}
31
+ name = Predicate.detect_and_strip_from_string!(str)
32
32
  predicate = Predicate.named(name)
33
33
  raise ArgumentError, "No valid predicate for #{key}" unless predicate
34
34
  attributes = str.split(/_and_|_or_/)
@@ -194,7 +194,7 @@ module Ransack
194
194
  def formatted_values_for_attribute(attr)
195
195
  casted_values_for_attribute(attr).map do |val|
196
196
  val = attr.ransacker.formatter.call(val) if attr.ransacker && attr.ransacker.formatter
197
- val = predicate.formatter.call(val) if predicate.formatter
197
+ val = predicate.format(val)
198
198
  val
199
199
  end
200
200
  end
@@ -173,7 +173,7 @@ module Ransack
173
173
 
174
174
  def strip_predicate_and_index(str)
175
175
  string = str.split(/\(/).first
176
- Ransack.predicate_keys.detect {|p| string.sub!(/_#{p}$/, '')}
176
+ Predicate.detect_and_strip_from_string!(string)
177
177
  string
178
178
  end
179
179
 
@@ -3,13 +3,35 @@ module Ransack
3
3
  attr_reader :name, :arel_predicate, :type, :formatter, :validator, :compound
4
4
 
5
5
  class << self
6
+
7
+ def names
8
+ Ransack.predicates.keys
9
+ end
10
+
11
+ def names_by_decreasing_length
12
+ names.sort {|a,b| b.length <=> a.length}
13
+ end
14
+
6
15
  def named(name)
7
16
  Ransack.predicates[name.to_s]
8
17
  end
9
18
 
19
+ def detect_and_strip_from_string!(str)
20
+ names_by_decreasing_length.detect {|p| str.sub!(/_#{p}$/, '')}
21
+ end
22
+
23
+ def detect_from_string(str)
24
+ names_by_decreasing_length.detect {|p| str.match(/_#{p}$/)}
25
+ end
26
+
27
+ def name_from_attribute_name(attribute_name)
28
+ names_by_decreasing_length.detect {|p| attribute_name.to_s.match(/_#{p}$/)}
29
+ end
30
+
10
31
  def for_attribute_name(attribute_name)
11
- self.named(Ransack.predicate_keys.detect {|p| attribute_name.to_s.match(/_#{p}$/)})
32
+ self.named(detect_from_string(attribute_name.to_s))
12
33
  end
34
+
13
35
  end
14
36
 
15
37
  def initialize(opts = {})
@@ -31,11 +53,19 @@ module Ransack
31
53
  name.hash
32
54
  end
33
55
 
56
+ def format(val)
57
+ if formatter
58
+ formatter.call(val)
59
+ else
60
+ val
61
+ end
62
+ end
63
+
34
64
  def validate(vals)
35
65
  if validator
36
66
  vals.select {|v| validator.call(v.value)}.any?
37
67
  else
38
- vals.select {|v| !v.blank?}.any?
68
+ vals.select {|v| v.present?}.any?
39
69
  end
40
70
  end
41
71
 
@@ -18,7 +18,7 @@ module Ransack
18
18
  original_name = key.to_s
19
19
  base_class = context.klass
20
20
  base_ancestors = base_class.ancestors.select { |x| x.respond_to?(:model_name) }
21
- predicate = Ransack.predicate_keys.detect {|p| original_name.match(/_#{p}$/)}
21
+ predicate = Predicate.detect_from_string(original_name)
22
22
  attributes_str = original_name.sub(/_#{predicate}$/, '')
23
23
  attribute_names = attributes_str.split(/_and_|_or_/)
24
24
  combinator = attributes_str.match(/_and_/) ? :and : :or
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Ernie Miller"]
10
10
  s.email = ["ernie@metautonomo.us"]
11
11
  s.homepage = "http://metautonomo.us/projects/ransack"
12
- s.summary = %q{Object-based searching. Like MetaSearch, but this time, with a better name.}
13
- s.description = %q{Not yet ready for public consumption.}
12
+ s.summary = %q{Object-based searching for ActiveRecord (currently).}
13
+ s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
14
14
 
15
15
  s.rubyforge_project = "ransack"
16
16
 
@@ -88,28 +88,28 @@ module Ransack
88
88
 
89
89
  it 'returns predicates with predicate_select' do
90
90
  html = @f.predicate_select
91
- Ransack.predicate_keys.each do |key|
91
+ Predicate.names.each do |key|
92
92
  html.should match /<option value="#{key}">/
93
93
  end
94
94
  end
95
95
 
96
96
  it 'filters predicates with single-value :only' do
97
97
  html = @f.predicate_select :only => 'eq'
98
- Ransack.predicate_keys.reject {|k| k =~ /^eq/}.each do |key|
98
+ Predicate.names.reject {|k| k =~ /^eq/}.each do |key|
99
99
  html.should_not match /<option value="#{key}">/
100
100
  end
101
101
  end
102
102
 
103
103
  it 'filters predicates with multi-value :only' do
104
104
  html = @f.predicate_select :only => [:eq, :lt]
105
- Ransack.predicate_keys.reject {|k| k =~ /^(eq|lt)/}.each do |key|
105
+ Predicate.names.reject {|k| k =~ /^(eq|lt)/}.each do |key|
106
106
  html.should_not match /<option value="#{key}">/
107
107
  end
108
108
  end
109
109
 
110
110
  it 'excludes compounds when :compounds => false' do
111
111
  html = @f.predicate_select :compounds => false
112
- Ransack.predicate_keys.select {|k| k =~ /_(any|all)$/}.each do |key|
112
+ Predicate.names.select {|k| k =~ /_(any|all)$/}.each do |key|
113
113
  html.should_not match /<option value="#{key}">/
114
114
  end
115
115
  end
metadata CHANGED
@@ -1,108 +1,106 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ransack
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
4
5
  prerelease:
5
- version: 0.4.1
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Ernie Miller
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-06-05 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2011-06-09 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
16
  name: activerecord
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &2161553440 !ruby/object:Gem::Requirement
19
18
  none: false
20
- requirements:
19
+ requirements:
21
20
  - - ~>
22
- - !ruby/object:Gem::Version
23
- version: "3.0"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
24
23
  type: :runtime
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
27
- name: activesupport
28
24
  prerelease: false
29
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: *2161553440
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: &2161552940 !ruby/object:Gem::Requirement
30
29
  none: false
31
- requirements:
30
+ requirements:
32
31
  - - ~>
33
- - !ruby/object:Gem::Version
34
- version: "3.0"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
35
34
  type: :runtime
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
38
- name: actionpack
39
35
  prerelease: false
40
- requirement: &id003 !ruby/object:Gem::Requirement
36
+ version_requirements: *2161552940
37
+ - !ruby/object:Gem::Dependency
38
+ name: actionpack
39
+ requirement: &2161552460 !ruby/object:Gem::Requirement
41
40
  none: false
42
- requirements:
41
+ requirements:
43
42
  - - ~>
44
- - !ruby/object:Gem::Version
45
- version: "3.0"
43
+ - !ruby/object:Gem::Version
44
+ version: '3.0'
46
45
  type: :runtime
47
- version_requirements: *id003
48
- - !ruby/object:Gem::Dependency
49
- name: rspec
50
46
  prerelease: false
51
- requirement: &id004 !ruby/object:Gem::Requirement
47
+ version_requirements: *2161552460
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &2161552000 !ruby/object:Gem::Requirement
52
51
  none: false
53
- requirements:
52
+ requirements:
54
53
  - - ~>
55
- - !ruby/object:Gem::Version
54
+ - !ruby/object:Gem::Version
56
55
  version: 2.5.0
57
56
  type: :development
58
- version_requirements: *id004
59
- - !ruby/object:Gem::Dependency
60
- name: machinist
61
57
  prerelease: false
62
- requirement: &id005 !ruby/object:Gem::Requirement
58
+ version_requirements: *2161552000
59
+ - !ruby/object:Gem::Dependency
60
+ name: machinist
61
+ requirement: &2161551540 !ruby/object:Gem::Requirement
63
62
  none: false
64
- requirements:
63
+ requirements:
65
64
  - - ~>
66
- - !ruby/object:Gem::Version
65
+ - !ruby/object:Gem::Version
67
66
  version: 1.0.6
68
67
  type: :development
69
- version_requirements: *id005
70
- - !ruby/object:Gem::Dependency
71
- name: faker
72
68
  prerelease: false
73
- requirement: &id006 !ruby/object:Gem::Requirement
69
+ version_requirements: *2161551540
70
+ - !ruby/object:Gem::Dependency
71
+ name: faker
72
+ requirement: &2161551080 !ruby/object:Gem::Requirement
74
73
  none: false
75
- requirements:
74
+ requirements:
76
75
  - - ~>
77
- - !ruby/object:Gem::Version
76
+ - !ruby/object:Gem::Version
78
77
  version: 0.9.5
79
78
  type: :development
80
- version_requirements: *id006
81
- - !ruby/object:Gem::Dependency
82
- name: sqlite3
83
79
  prerelease: false
84
- requirement: &id007 !ruby/object:Gem::Requirement
80
+ version_requirements: *2161551080
81
+ - !ruby/object:Gem::Dependency
82
+ name: sqlite3
83
+ requirement: &2161550620 !ruby/object:Gem::Requirement
85
84
  none: false
86
- requirements:
85
+ requirements:
87
86
  - - ~>
88
- - !ruby/object:Gem::Version
87
+ - !ruby/object:Gem::Version
89
88
  version: 1.3.3
90
89
  type: :development
91
- version_requirements: *id007
92
- description: Not yet ready for public consumption.
93
- email:
90
+ prerelease: false
91
+ version_requirements: *2161550620
92
+ description: Ransack is the successor to the MetaSearch gem. It improves and expands
93
+ upon MetaSearch's functionality, but does not have a 100%-compatible API.
94
+ email:
94
95
  - ernie@metautonomo.us
95
96
  executables: []
96
-
97
97
  extensions: []
98
-
99
98
  extra_rdoc_files: []
100
-
101
- files:
99
+ files:
102
100
  - .gitignore
103
101
  - Gemfile
104
102
  - LICENSE
105
- - README.rdoc
103
+ - README.md
106
104
  - Rakefile
107
105
  - lib/ransack.rb
108
106
  - lib/ransack/adapters/active_record.rb
@@ -156,34 +154,32 @@ files:
156
154
  - spec/spec_helper.rb
157
155
  - spec/support/en.yml
158
156
  - spec/support/schema.rb
157
+ has_rdoc: true
159
158
  homepage: http://metautonomo.us/projects/ransack
160
159
  licenses: []
161
-
162
160
  post_install_message:
163
161
  rdoc_options: []
164
-
165
- require_paths:
162
+ require_paths:
166
163
  - lib
167
- required_ruby_version: !ruby/object:Gem::Requirement
164
+ required_ruby_version: !ruby/object:Gem::Requirement
168
165
  none: false
169
- requirements:
170
- - - ">="
171
- - !ruby/object:Gem::Version
172
- version: "0"
173
- required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ! '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
171
  none: false
175
- requirements:
176
- - - ">="
177
- - !ruby/object:Gem::Version
178
- version: "0"
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
179
176
  requirements: []
180
-
181
177
  rubyforge_project: ransack
182
- rubygems_version: 1.7.2
178
+ rubygems_version: 1.6.2
183
179
  signing_key:
184
180
  specification_version: 3
185
- summary: Object-based searching. Like MetaSearch, but this time, with a better name.
186
- test_files:
181
+ summary: Object-based searching for ActiveRecord (currently).
182
+ test_files:
187
183
  - spec/blueprints/articles.rb
188
184
  - spec/blueprints/comments.rb
189
185
  - spec/blueprints/notes.rb
@@ -1,5 +0,0 @@
1
- = Ransack
2
-
3
- Don't use me.
4
-
5
- Seriously, I'm not anywhere close to ready for public consumption, yet.