1
0
Fork 0
mirror of https://github.com/activerecord-hackery/ransack.git synced 2022-11-09 13:47:45 -05:00

merged travis.yml, Gemfile and Context

This commit is contained in:
Zhomart Mukhamejanov 2014-10-06 14:05:02 -07:00
commit aeeaf597f6
15 changed files with 473 additions and 166 deletions

View file

@ -7,9 +7,13 @@ sudo: false
rvm:
- 2.1
- 2.0
- 1.9.3
- 1.9
env:
- RAILS=master DB=mongodb
- RAILS=master DB=sqlite3
- RAILS=master DB=mysql
- RAILS=master DB=postgres
- RAILS=4-1-stable DB=mongodb
- RAILS=4-1-stable DB=sqlite3
- RAILS=4-1-stable DB=mysql

View file

@ -1,27 +1,67 @@
# Change Log
All notable changes to this project from August 2014 on will be documented here.
This change log was started in August 2014. All notable changes to this project
henceforth should be documented here.
## Unreleased
### Added
* Add `ro.yml` Romanian translation file.
* `sort_link` helper: Add support for multiple sort fields and default orders
([pull request](https://github.com/activerecord-hackery/ransack/pull/438)).
*Andreas Philippi*
*Caleb Land*, *James u007*
### Fixed
### Changed
* Reduce object allocations and memory footprint (with a slight speed gain as
well) by extracting commonly used strings into top level constants and
replacing calls to `#try` methods with simple nil checking.
*Jon Atack*
## Version 1.4.1 - 2014-09-23
### Fixed
* Fix README markdown so RubyGems documentation picks up the formatting correctly.
*Jon Atack*
## Version 1.4.0 - 2014-09-23
### Added
* Add support for Rails 4.2.0! Let us know if you encounter any issues.
*Xiang Li*
* Add `not_true` and `not_false` predicates and update the "Basic Searching"
wiki. Fixes #123, #353.
*Pedro Chambino*
* Start a CHANGELOG.
* Add `ro.yml` Romanian translation file.
*Andreas Philippi*
* Add new documentation in the README explaining how to group queries by `OR`
instead of the default `AND` using the `m: 'or'` combinator.
* Add new documentation in the README and in the source code comments
explaining in detail how to handle whitelisting/authorization of
attributes, associations, sorts and scopes.
* Add new documentation in the README explaining in more detail how to use
scopes for searching with Ransack.
* Begin a CHANGELOG.
*Jon Atack*
### Fixed
* Fix attribute translations when using ActiveRecord with STI.
* Fix singular/plural Active Record attribute translations.
*Andreas Philippi*
@ -29,7 +69,7 @@ All notable changes to this project from August 2014 on will be documented here.
*Daniel Rikowski*
* Apply default scope conditions for association joins (Rails 3).
* Apply default scope conditions for association joins (fix for Rails 3).
Avoid selecting records from joins that would normally be filtered out
if they were selected from the base table. Only applies to Rails 3, as
@ -37,8 +77,8 @@ All notable changes to this project from August 2014 on will be documented here.
*Andrew Vit*
* Fix incoherent code examples in the README Associations section that mixed
up `@q` and `@search`.
* Fix incoherent code examples in the README Associations section that
sometimes used `@q` and other times `@search`.
*Jon Atack*
@ -46,9 +86,9 @@ All notable changes to this project from August 2014 on will be documented here.
* Refactor Ransack::Translate.
* Rewrite much of the README doc, including the Associations section
code examples and the Authorizations section showing how to whitelist
attributes, associations, sorts and scopes.
* Rewrite much of the Ransack README documentation, including the
Associations section code examples and the Authorizations section detailing
how to whitelist attributes, associations, sorts and scopes.
*Jon Atack*

View file

@ -3,12 +3,15 @@ gemspec
gem 'rake'
rails = ENV['RAILS'] || '4-1-stable'
rails = ENV['RAILS'] || 'master'
gem 'polyamorous', '~> 1.1'
gem 'pry'
# Provide timezone information on Windows
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
case rails
when /\// # A path
gem 'activesupport', path: "#{rails}/activesupport"

157
README.md
View file

@ -26,47 +26,34 @@ instead.
## Getting started
Because ActiveRecord has been evolving quite a bit, your friendly Ransack is
available in several flavors! Take your pick:
Ransack is currently compatible with Rails 3.x, 4.0, 4.1 and 4.2.
In your Gemfile, for the last officially released gem compatible with Rails
3.x, 4.0 and 4.1 (for Rails 4.2, use the dedicated `rails-4.2` branch described
below for now):
In your Gemfile, for the last officially released Ransack gem:
```ruby
gem 'ransack'
```
Or if you want to use the latest updates on the Ransack master branch:
Or, if you would like to use the latest updates:
```ruby
gem 'ransack', github: 'activerecord-hackery/ransack'
```
If you are using Rails 4.1, you may prefer the dedicated [Rails 4.1 branch]
(https://github.com/activerecord-hackery/ransack/tree/rails-4.1) which
usually contains the latest updates on master (albeit sometimes with some
delay), supports only 4.1, and is lighter and somewhat faster:
The other branches (`rails-4`, `rails-4.1`, and `rails-4.2`) were each used for
developing and running Ransack with the latest upcoming version of Rails at the
time. They are smaller and possibly slightly faster because they do not have to
support previous versions of Rails and Active Record. Once support for that
Rails version is merged from the branch into Ransack master, the branch is no
longer actively maintained -- unless the open source community submits pull
requests to maintain them. You are welcome to do so!
To use one of the branches, for example the `rails-4.1` branch:
```ruby
gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4.1'
```
Similarly, if you are using Rails 4.0, you may prefer the dedicated
[Rails 4 branch](https://github.com/activerecord-hackery/ransack/tree/rails-4)
for the same reasons:
```ruby
gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4'
```
Last but definitely not least, an experimental [Rails 4.2 branch]
(https://github.com/activerecord-hackery/ransack/tree/rails-4.2) is available:
```ruby
gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4.2'
```
## Usage
Ransack can be used in one of two modes, simple or advanced.
@ -90,15 +77,18 @@ If you're coming from MetaSearch, things to note:
3. Common ActiveRecord::Relation methods are no longer delegated by the
search object. Instead, you will get your search results (an
ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
`Search#result`. If passed `distinct: true`, `result` will generate a `SELECT
DISTINCT` to avoid returning duplicate rows, even if conditions on a join
would otherwise result in some.
`Search#result`.
4. If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
avoid returning duplicate rows, even if conditions on a join would otherwise
result in some.
Please note that for many databases, a sort on an associated table's columns
may result in invalid SQL with `distinct: true` -- in those cases, you're on
your own, and will need to modify the result as needed to allow these queries
to work. One good workaround if `distinct: true` is causing problems, can be to
not use it and call `#to_a.uniq` on your final collection instead.
to work. If `distinct: true` is causing you problems, another way to remove
duplicates is to call `#to_a.uniq` on your collection instead (see the next
section below).
####In your controller
@ -126,7 +116,7 @@ The two primary Ransack view helpers are `search_form_for` and `sort_link`,
which are defined in
[Ransack::Helpers::FormHelper](lib/ransack/helpers/form_helper.rb).
#####1. Ransack's `search_form_for` helper replaces `form_for` for creating the view search form:
#####Ransack's `search_form_for` helper replaces `form_for` for creating the view search form:
```erb
<%= search_form_for @q do |f| %>
@ -161,18 +151,39 @@ The `search_form_for` answer format can be set like this:
<%= search_form_for(@q, format: :json) do |f| %>
```
#####2. Ransack's `sort_link` helper creates table headers that are sortable links:
#####Ransack's `sort_link` helper creates table headers that are sortable links:
```erb
<%= content_tag :th, sort_link(@q, :name) %>
<%= sort_link(@q, :name) %>
```
Additional options can be passed after the column attribute, like a different
column title or a default sort order:
```erb
<%= content_tag :th, sort_link(@q, :name, 'Last Name', default_order: :desc) %>
<%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
```
You can also sort on multiple fields by specifying an ordered array:
```erb
<%= sort_link(@q, :last_name, [:last_name, 'first_name asc'], 'Last Name') %>
```
In the example above, clicking the link will sort by `last_name` and then
`first_name`. Specifying the sort direction on a field in the array tells
Ransack to _always_ sort that particular field in the specified direction.
Multiple `default_order` fields may also be specified with a hash:
```erb
<%= sort_link(@q, :last_name, [:last_name, :first_name],
default_order: { last_name: 'asc', first_name: 'desc' }) %>
```
This example toggles the sort directions of both fields, by default
initially sorting the `last_name` field by ascending order, and the
`first_name` field by descending order.
### Advanced Mode
"Advanced" searches (ab)use Rails' nested attributes functionality in order to
@ -307,29 +318,37 @@ class methods in your models to apply selective authorization:
Here is how these four methods are implemented in Ransack:
```ruby
def ransackable_attributes(auth_object = nil)
# By default returns all column names and any defined ransackers as strings.
# For overriding with a whitelist of strings.
column_names + _ransackers.keys
end
# Ransackable_attributes, by default, returns all column names
# and any defined ransackers as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_attributes(auth_object = nil)
column_names + _ransackers.keys
end
def ransackable_associations(auth_object = nil)
# By default returns the names of all associations as strings.
# For overriding with a whitelist of strings.
reflect_on_all_associations.map { |a| a.name.to_s }
end
# Ransackable_associations, by default, returns the names
# of all associations as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_associations(auth_object = nil)
reflect_on_all_associations.map { |a| a.name.to_s }
end
def ransortable_attributes(auth_object = nil)
# By default returns the names of all attributes for sorting.
# For overriding with a whitelist of strings.
ransackable_attributes(auth_object)
end
# Ransortable_attributes, by default, returns the names
# of all attributes available for sorting as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransortable_attributes(auth_object = nil)
ransackable_attributes(auth_object)
end
def ransackable_scopes(auth_object = nil)
# By default returns an empty array, i.e. no class methods/scopes
# are authorized. For overriding with a whitelist of *symbols*.
[]
end
# Ransackable_scopes, by default, returns an empty array
# i.e. no class methods/scopes are authorized.
# For overriding with a whitelist array of *symbols*.
#
def ransackable_scopes(auth_object = nil)
[]
end
```
Any values not returned from these methods will be ignored by Ransack, i.e.
@ -345,8 +364,8 @@ Here is an example that puts all this together, adapted from
(http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
In an `Article` model, add the following `ransackable_attributes` class method
(preferably private):
```ruby
# article.rb
class Article < ActiveRecord::Base
private
@ -362,9 +381,10 @@ class Article < ActiveRecord::Base
end
end
```
Here is example code for the `articles_controller`:
```ruby
# articles_controller.rb
class ArticlesController < ApplicationController
def index
@ -379,7 +399,9 @@ class ArticlesController < ApplicationController
end
end
```
Trying it out in `rails console`:
```ruby
> Article
=> Article(id: integer, person_id: integer, title: string, body: text)
@ -399,18 +421,20 @@ Trying it out in `rails console`:
> Article.search({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
=> SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
```
That's it! Now you know how to whitelist/blacklist various elements in Ransack.
### Using Scopes/Class Methods
Continuing on from the preceding section, searching by scopes requires defining
a whitelist of `ransackable_scopes` on the model class. By default, all class
methods (e.g. scopes) are ignored. Scopes will be applied for matching `true`
values, or for given values if the scope accepts a value:
a whitelist of `ransackable_scopes` on the model class. The whitelist should be
an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
Scopes will be applied for matching `true` values, or for given values if the
scope accepts a value:
```ruby
class Employee < ActiveRecord::Base
scope :active, ->(boolean = true) { (where active: boolean) }
scope :active, ->(boolean = true) { where(active: boolean) }
scope :salary_gt, ->(amount) { where('salary > ?', amount) }
# Scopes are just syntactical sugar for class methods, which may also be used:
@ -437,6 +461,19 @@ Employee.search({ active: true, hired_since: '2013-01-01' })
Employee.search({ salary_gt: 100_000 }, { auth_object: current_user })
```
Scopes are a recent addition to Ransack and currently have a few caveats:
First, a scope involving child associations needs to be defined in the parent
table model, not in the child model. Second, scopes with an array as an
argument are not easily usable yet, because the array currently needs to be
wrapped in an array to function (see
[this issue](https://github.com/activerecord-hackery/ransack/issues/404)),
which is not compatible with Ransack form helpers. For this use case, it may be
better for now to use [ransackers]
(https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead
where feasible. Finally, there is also
[this issue](https://github.com/activerecord-hackery/ransack/issues/403)
to be aware of. Pull requests with solutions and tests are welcome!
### Grouping queries by OR instead of AND
The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the

View file

@ -20,27 +20,35 @@ module Ransack
.new(self, name, opts, &block)
end
# Ransackable_attributes, by default, returns all column names
# and any defined ransackers as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_attributes(auth_object = nil)
# By default returns all column names and any defined ransackers
# as strings. For overriding with a whitelist of strings.
column_names + _ransackers.keys
end
# Ransackable_associations, by default, returns the names
# of all associations as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_associations(auth_object = nil)
# By default returns the names of all associations as strings.
# For overriding with a whitelist of strings.
reflect_on_all_associations.map { |a| a.name.to_s }
end
# Ransortable_attributes, by default, returns the names
# of all attributes available for sorting as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransortable_attributes(auth_object = nil)
# By default returns the names of all attributes for sorting.
# For overriding with a whitelist of strings.
ransackable_attributes(auth_object)
end
# Ransackable_scopes, by default, returns an empty array
# i.e. no class methods/scopes are authorized.
# For overriding with a whitelist array of *symbols*.
#
def ransackable_scopes(auth_object = nil)
# By default returns an empty array, i.e. no class methods/scopes
# are authorized. For overriding with a whitelist of symbols.
[]
end

View file

@ -25,7 +25,7 @@ module Ransack
@object = relation_for(object)
@klass = @object.klass
@join_dependency = join_dependency(@object)
@join_type = options[:join_type] || Arel::OuterJoin
@join_type = options[:join_type] || Polyamorous::OuterJoin
@search_key = options[:search_key] || Ransack.options[:search_key]
if ::ActiveRecord::VERSION::STRING >= "4.1"

View file

@ -1,6 +1,22 @@
require 'ransack/constants'
require 'ransack/predicate'
ASC = 'asc'.freeze
DESC = 'desc'.freeze
ASC_DESC = %w(asc desc).freeze
ASC_ARROW = '&#9650;'.freeze
DESC_ARROW = '&#9660;'.freeze
OR = 'or'.freeze
AND = 'and'.freeze
SORT = 'sort'.freeze
SORT_LINK = 'sort_link'.freeze
SUFFIXES = %w(_any _all).freeze
ATTRIBUTE = 'attribute'.freeze
SEARCH = 'search'.freeze
DEFAULT_SEARCH_KEY = 'q'.freeze
SPACE = ' '.freeze
NON_BREAKING_SPACE = '&nbsp;'.freeze
module Ransack
module Configuration
@ -24,7 +40,7 @@ module Ransack
self.predicates[name] = Predicate.new(opts)
['_any', '_all'].each do |suffix|
SUFFIXES.each do |suffix|
compound_name = name + suffix
self.predicates[compound_name] = Predicate.new(
opts.merge(

View file

@ -27,7 +27,7 @@ module Ransack
def attribute_select(options = nil, html_options = nil, action = nil)
options = options || {}
html_options = html_options || {}
action = action || 'search'
action = action || SEARCH
default = options.delete(:default)
raise ArgumentError, formbuilder_error_message(
"#{action}_select") unless object.respond_to?(:context)
@ -55,7 +55,7 @@ module Ransack
end
def sort_select(options = {}, html_options = {})
attribute_select(options, html_options, 'sort') +
attribute_select(options, html_options, SORT) +
sort_direction_select(options, html_options)
end
@ -157,14 +157,14 @@ module Ransack
end
def sort_array
[['asc', object.translate('asc')], ['desc', object.translate('desc')]]
[[ASC, object.translate(ASC)], [DESC, object.translate(DESC)]]
end
def combinator_choices
if Nodes::Condition === object
[['or', Translate.word(:any)], ['and', Translate.word(:all)]]
[[OR, Translate.word(:any)], [AND, Translate.word(:all)]]
else
[['and', Translate.word(:all)], ['or', Translate.word(:any)]]
[[AND, Translate.word(:all)], [OR, Translate.word(:any)]]
end
end
@ -231,7 +231,7 @@ module Ransack
end
def formbuilder_error_message(action)
"#{action.sub('search', 'attribute')
"#{action.sub(SEARCH, ATTRIBUTE)
} must be called inside a search FormBuilder!"
end

View file

@ -2,26 +2,6 @@ module Ransack
module Helpers
module FormHelper
def asc
'asc'.freeze
end
def desc
'desc'.freeze
end
def asc_arrow
'&#9650;'.freeze
end
def desc_arrow
'&#9660;'.freeze
end
def non_breaking_space
'&nbsp;'.freeze
end
def search_form_for(record, options = {}, &proc)
if record.is_a?(Ransack::Search)
search = record
@ -48,13 +28,14 @@ module Ransack
"#{search.klass.to_s.underscore}_search",
:method => :get
}
options[:as] ||= 'q'.freeze
options[:as] ||= DEFAULT_SEARCH_KEY
options[:html].reverse_merge!(html_options)
options[:builder] ||= FormBuilder
form_for(record, options, &proc)
end
# sort_link @q, :name, [:name, 'kind ASC'], 'Player Name'
def sort_link(search, attribute, *args)
# Extract out a routing proxy for url_for scoping later
if search.is_a?(Array)
@ -65,65 +46,106 @@ module Ransack
raise TypeError, "First argument must be a Ransack::Search!" unless
Search === search
search_params = params[search.context.search_key].presence ||
{}.with_indifferent_access
# This is the field that this link represents. The direction of the sort icon (up/down arrow) will
# depend on the sort status of this field
field_name = attribute.to_s
attr_name = attribute.to_s
# Determine the fields we want to sort on
sort_fields = if Array === args.first
args.shift
else
Array(field_name)
end
name = (
if args.size > 0 && !args.first.is_a?(Hash)
args.shift.to_s
else
Translate.attribute(attr_name, :context => search.context)
end
)
if existing_sort = search.sorts.detect { |s| s.name == attr_name }
prev_attr, prev_dir = existing_sort.name, existing_sort.dir
label_text =
if !args.first.try(:is_a?, Hash)
args.shift.to_s
else
Translate.attribute(field_name, :context => search.context)
end
options = args.first.is_a?(Hash) ? args.shift.dup : {}
default_order = options.delete :default_order
current_dir = prev_attr == attr_name ? prev_dir : nil
default_order_is_a_hash = Hash === default_order
if current_dir
new_dir = current_dir == desc ? asc : desc
else
new_dir = default_order || asc
# If the default order is a hash of fields, duplicate it and let us access it with strings or symbols
default_order = default_order.dup.with_indifferent_access if
default_order_is_a_hash
search_params = params[search.context.search_key].presence ||
{}.with_indifferent_access
# Find the current direction (if there is one) of the primary sort field
if existing_sort = search.sorts.detect { |s| s.name == field_name }
field_current_dir = existing_sort.dir
end
sort_params = []
Array(sort_fields).each do |sort_field|
attr_name, new_dir = sort_field.to_s.downcase.split(/\s+/)
current_dir = nil
# if the user didn't specify the sort direction, detect the previous
# sort direction on this field and reverse it
if ASC_DESC.none? { |d| d == new_dir }
if existing_sort = search.sorts.detect { |s| s.name == attr_name }
current_dir = existing_sort.dir
end
new_dir =
if current_dir
current_dir == DESC ? ASC : DESC
elsif default_order_is_a_hash
default_order[attr_name] || ASC
else
default_order || ASC
end
end
sort_params << "#{attr_name} #{new_dir}"
end
# if there is only one sort parameter, remove it from the array and just
# use the string as the parameter
sort_params = sort_params.first if sort_params.size == 1
html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
css = ['sort_link', current_dir].compact.join(' ')
html_options[:class] = [css, html_options[:class]].compact.join(' ')
css = [SORT_LINK, field_current_dir].compact.join(SPACE)
html_options[:class] = [css, html_options[:class]].compact.join(SPACE)
query_hash = {}
query_hash[search.context.search_key] = search_params
.merge(:s => "#{attr_name} #{new_dir}")
.merge(:s => sort_params)
options.merge!(query_hash)
options_for_url = params.merge options
options_for_url = params.merge(options)
url = if routing_proxy && respond_to?(routing_proxy)
url =
if routing_proxy && respond_to?(routing_proxy)
send(routing_proxy).url_for(options_for_url)
else
url_for(options_for_url)
end
link_to(
[ERB::Util.h(name), order_indicator_for(current_dir)]
.compact
.join(non_breaking_space)
.html_safe,
url,
html_options
)
name = link_name(label_text, field_current_dir)
link_to(name, url, html_options)
end
private
def order_indicator_for(order)
if order == asc
asc_arrow
elsif order == desc
desc_arrow
def link_name(label_text, dir)
[ERB::Util.h(label_text), order_indicator_for(dir)]
.compact
.join(NON_BREAKING_SPACE)
.html_safe
end
def order_indicator_for(dir)
if dir == ASC
ASC_ARROW
elsif dir == DESC
DESC_ARROW
else
nil
end

View file

@ -35,8 +35,8 @@ module Ransack
end
def dir=(dir)
dir = dir.try(:downcase)
@dir = %w(asc desc).include?(dir) ? dir : 'asc'
dir = dir.downcase if dir
@dir = ASC_DESC.include?(dir) ? dir : ASC
end
end

View file

@ -135,8 +135,9 @@ module Ransack
end
def self.translated_attribute(associated_class)
"#{associated_class.i18n_scope}.attributes.#{
i18n_key(associated_class)}.#{@attr_name}".to_sym
key = "#{associated_class.i18n_scope}.attributes.#{
i18n_key(associated_class)}.#{@attr_name}"
["#{key}.one".to_sym, key.to_sym]
end
def self.translated_ancestor_attributes

View file

@ -1,3 +1,3 @@
module Ransack
VERSION = "1.3.0"
VERSION = "1.4.1"
end

View file

@ -3,11 +3,16 @@ require 'spec_helper'
module Ransack
module Adapters
module ActiveRecord
version = ::ActiveRecord::VERSION
AR_version = "#{version::MAJOR}.#{version::MINOR}"
describe Context do
subject { Context.new(Person) }
if ::ActiveRecord::VERSION::STRING >= "3.1"
its(:alias_tracker) { should be_a ::ActiveRecord::Associations::AliasTracker }
if AR_version >= "3.1"
its(:alias_tracker) {
should be_a ::ActiveRecord::Associations::AliasTracker
}
end
describe '#relation_for' do
@ -22,7 +27,8 @@ module Ransack
result = subject.evaluate(search)
expect(result).to be_an ::ActiveRecord::Relation
expect(result.to_sql).to match /#{quote_column_name("name")} = 'Joe Blow'/
expect(result.to_sql)
.to match /#{quote_column_name("name")} = 'Joe Blow'/
end
it 'SELECTs DISTINCT when distinct: true' do
@ -38,12 +44,15 @@ module Ransack
let(:shared_context) { Context.for(Person) }
before do
Search.new(Person, {:parent_name_eq => 'A'}, context: shared_context)
Search.new(Person, {:children_name_eq => 'B'}, context: shared_context)
Search.new(Person, { :parent_name_eq => 'A' },
context: shared_context)
Search.new(Person, { :children_name_eq => 'B' },
context: shared_context)
end
describe '#join_associations', :if => ::ActiveRecord::VERSION::STRING <= '4.0' do
it 'returns dependent join associations for all searches run against the context' do
describe '#join_associations', :if => AR_version <= '4.0' do
it 'returns dependent join associations for all searches run
against the context' do
parents, children = shared_context.join_associations
expect(children.aliased_table_name).to eq "children_people"
@ -53,22 +62,28 @@ module Ransack
it 'can be rejoined to execute a valid query' do
parents, children = shared_context.join_associations
expect { Person.joins(parents).joins(children).to_a }.to_not raise_error
expect { Person.joins(parents).joins(children).to_a }
.to_not raise_error
end
end
describe '#join_sources', :if => ::ActiveRecord::VERSION::STRING >= '3.1' do
it 'returns dependent arel join nodes for all searches run against the context' do
describe '#join_sources' do
# FIXME: fix this test for Rails 4.2.
it 'returns dependent arel join nodes for all searches run against
the context',
:if => %w(3.1 3.2 4.0 4.1).include?(AR_version) do
parents, children = shared_context.join_sources
expect(children.left.name).to eq "children_people"
expect(parents.left.name).to eq "parents_people"
end
it 'can be rejoined to execute a valid query' do
it 'can be rejoined to execute a valid query',
:if => AR_version >= '3.1' do
parents, children = shared_context.join_sources
expect { Person.joins(parents).joins(children).to_a }.to_not raise_error
expect { Person.joins(parents).joins(children).to_a }
.to_not raise_error
end
end
end

View file

@ -51,8 +51,8 @@ module Ransack
end
describe '#sort_link with default search_key defined as symbol' do
subject { @controller.
view_context.sort_link(
subject { @controller.view_context
.sort_link(
Person.search(
{ :sorts => ['name desc'] }, :search_key => :people_search
),
@ -71,6 +71,46 @@ module Ransack
}
end
describe '#sort_link desc through association table defined as a symbol' do
subject { @controller.view_context
.sort_link(
Person.search({ :sorts => ['comments_body asc'] }),
:comments_body, :controller => 'people'
)
}
it {
should match(
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
/people\?q%5Bs%5D=comments.body\+desc/
else
/people\?q(%5B|\[)s(%5D|\])=comments.body\+desc/
end
)
}
it { should match /sort_link asc/ }
it { should match /Body&nbsp;&#9650;/ }
end
describe '#sort_link through association table defined as a string' do
subject { @controller.view_context
.sort_link(
Person.search({ :sorts => ['comments.body desc'] }),
'comments.body', :controller => 'people'
)
}
it {
should match(
if ActiveRecord::VERSION::STRING =~ /^3\.[1-2]\./
/people\?q%5Bs%5D=comments.body\+asc/
else
/people\?q(%5B|\[)s(%5D|\])=comments.body\+asc/
end
)
}
it { should match /sort_link desc/ }
it { should match /Comments.body&nbsp;&#9660;/ }
end
describe '#sort_link works even if search params are a blank string' do
before { @controller.view_context.params[:q] = '' }
specify {
@ -105,6 +145,127 @@ module Ransack
}
end
describe '#sort_link with multiple search_keys defined as an array' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
:name, [:name, 'email DESC'],
:controller => 'people'
)
}
it {
should match(
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
)
}
it {
should match /sort_link desc/
}
it {
should match /Full Name&nbsp;&#9660;/
}
end
describe '#sort_link with multiple search_keys should allow a label to be specified' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
:name, [:name, 'email DESC'],
'Property Name',
:controller => 'people'
)
}
it {
should match /Property Name&nbsp;&#9660;/
}
end
describe '#sort_link with multiple search_keys should flip multiple fields specified without a direction' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search(:sorts => ['name desc', 'email asc'])],
:name, [:name, :email],
:controller => 'people'
)
}
it {
should match(
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+asc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
)
}
it {
should match /sort_link desc/
}
it {
should match /Full Name&nbsp;&#9660;/
}
end
describe '#sort_link with multiple search_keys should allow a default_order to be specified' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search()],
:name, [:name, :email],
:controller => 'people',
:default_order => 'desc'
)
}
it {
should match(
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
)
}
it {
should match /sort_link/
}
it {
should match /Full Name/
}
end
describe '#sort_link with multiple search_keys should allow multiple default_orders to be specified' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search()],
:name, [:name, :email],
:controller => 'people',
:default_order => { 'name' => 'desc', :email => 'asc' }
)
}
it {
should match(
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+asc/
)
}
it {
should match /sort_link/
}
it {
should match /Full Name/
}
end
describe '#sort_link with multiple search_keys with multiple default_orders should not override a specified order' do
subject { @controller.view_context
.sort_link(
[:main_app, Person.search()],
:name, [:name, 'email desc'],
:controller => 'people',
:default_order => { 'name' => 'desc', :email => 'asc' }
)
}
it {
should match(
/people\?q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=name\+desc&amp;q(%5B|\[)s(%5D|\])(%5B|\[)(%5D|\])=email\+desc/
)
}
it {
should match /sort_link/
}
it {
should match /Full Name/
}
end
context 'view has existing parameters' do
before do
@controller.view_context.params.merge!({ :exist => 'existing' })

View file

@ -135,7 +135,7 @@ module Schema
t.string :only_admin
t.integer :salary
t.boolean :awesome, default: false
t.timestamps
t.timestamps null: false
end
create_table :articles, :force => true do |t|