ransack 0.4.1 → 0.4.2

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