elastic_ransack 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/elastic_ransack.gemspec +26 -0
- data/lib/elastic_ransack.rb +45 -0
- data/lib/elastic_ransack/core_ext.rb +9 -0
- data/lib/elastic_ransack/model.rb +50 -0
- data/lib/elastic_ransack/naming.rb +52 -0
- data/lib/elastic_ransack/predicate.rb +12 -0
- data/lib/elastic_ransack/search.rb +103 -0
- data/lib/elastic_ransack/version.rb +3 -0
- data/spec/elastic_ransack/base_spec.rb +42 -0
- data/spec/elastic_ransack/core_ext_spec.rb +18 -0
- data/spec/elastic_ransack/predicate_spec.rb +12 -0
- data/spec/elastic_ransack/search_spec.rb +95 -0
- data/spec/elastic_ransack/sorting_spec.rb +20 -0
- data/spec/spec_helper.rb +103 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f63dff989c1fc3d17945abaa3bdea9dbde2d5481
|
4
|
+
data.tar.gz: 567674e6eb6459ca4bda929cb9829c8698741289
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 007262a391eb9046e8acdd188fd60b5c890a81f6f74784ac0beeae71d5392dcbbfe239b7c99058944cad5d851bc840b779ab87628b47159368bc843d4b0ae13c
|
7
|
+
data.tar.gz: e56a8010464704f31371314aa69f70b03b111ad483437ae68b4738dfee974a5876bcbd2a7b92d5c889fbe19da1be5e7232e90a248dd6961b83ae22eb20203000
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Alex Leschenko
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# ElasticRansack
|
2
|
+
|
3
|
+
ElasticRansack provides condition predicate searching to your elasticsearch models like ransack or meta_search gems.
|
4
|
+
Your can just create serach form with `name_cont` or `created_at_gt` fields and elastic_ransack build a search query for you.
|
5
|
+
It is compatible with most of the Ransack helper methods and predicates.
|
6
|
+
|
7
|
+
It uses [Tire](https://github.com/karmi/tire) and [Elasticsearch](http://www.elasticsearch.org/) for searching.
|
8
|
+
|
9
|
+
Inspired by [Ransack](https://github.com/ernie/ransack) gem.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'elastic_ransack'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install elastic_ransack
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Include ElasticRansack extension to your model:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class User < ActiveRecord::Base
|
31
|
+
include ElasticRansack::Model
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Search with `elastic_ransack` method. It return `Tire::Results::Collection` instance:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
User.elastic_ransack({name_cont: 'alex', role_id_eq: 1, state_id_in: [2, 3], created_at_gt: 1.day.ago})
|
39
|
+
```
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elastic_ransack/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'elastic_ransack'
|
8
|
+
spec.version = ElasticRansack::VERSION
|
9
|
+
spec.authors = ['Alex Leschenko']
|
10
|
+
spec.email = %w(leschenko.al@gmail.com)
|
11
|
+
spec.summary = %q{Conditions searching adapter for elasticsearch models like ransack}
|
12
|
+
spec.description = %q{Conditions searching using predicates such as 'name_cont' or 'created_at_gt' for elasticsearch models}
|
13
|
+
spec.homepage = 'https://github.com/leschenko/elastic_ransack'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = %w(lib)
|
20
|
+
|
21
|
+
spec.add_dependency 'tire', '~> 0.6.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'tire'
|
2
|
+
require 'elastic_ransack/version'
|
3
|
+
require 'elastic_ransack/core_ext'
|
4
|
+
require 'elastic_ransack/predicate'
|
5
|
+
require 'elastic_ransack/naming'
|
6
|
+
require 'elastic_ransack/search'
|
7
|
+
require 'elastic_ransack/model'
|
8
|
+
|
9
|
+
module ElasticRansack
|
10
|
+
mattr_accessor :predicates
|
11
|
+
self.predicates = []
|
12
|
+
|
13
|
+
BASE_PREDICATES = [
|
14
|
+
['eq', {query: proc { |attr, v| {term: {attr => v}} }}],
|
15
|
+
['in', {query: proc { |attr, v| {terms: {attr => ElasticRansack.val_to_array(v)}} }}],
|
16
|
+
['in_all', {query: proc { |attr, v| {terms: {attr => ElasticRansack.val_to_array(v), execution: 'and'}} }}],
|
17
|
+
['gt', {query: proc { |attr, v| {range: {attr => {gt: v}}} }}],
|
18
|
+
['lt', {query: proc { |attr, v| {range: {attr => {lt: v}}} }}],
|
19
|
+
['gteq', {query: proc { |attr, v| {range: {attr => {gte: v}}} }}],
|
20
|
+
['lteq', {query: proc { |attr, v| {range: {attr => {lte: v}}} }}]
|
21
|
+
]
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def setup
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_predicate(*args)
|
29
|
+
self.predicates << Predicate.new(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def val_to_array(val)
|
33
|
+
return [] unless val
|
34
|
+
return val if val.is_a?(Array)
|
35
|
+
val.to_s.split(',').map(&:to_i).reject(&:zero?)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
ElasticRansack.setup do |config|
|
41
|
+
ElasticRansack::BASE_PREDICATES.each do |args|
|
42
|
+
config.add_predicate *args
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ElasticRansack
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# === options
|
11
|
+
# [:s]
|
12
|
+
# sorting column
|
13
|
+
# can contain sort mode separated by space
|
14
|
+
# example: 'id desc'
|
15
|
+
# [:q_cont]
|
16
|
+
# search against _all fields
|
17
|
+
#
|
18
|
+
# ==== condition predicates:
|
19
|
+
# '_cont' contains string value
|
20
|
+
# '_eq' equal value
|
21
|
+
# '_in' include any of the values
|
22
|
+
# '_in_all' include all values
|
23
|
+
# '_gt' greater then value
|
24
|
+
# '_lt' less then value
|
25
|
+
# '_gteq' greater or equal the value
|
26
|
+
# '_lteq ' less or equal the value
|
27
|
+
#
|
28
|
+
# === search_options
|
29
|
+
# [:globalize]
|
30
|
+
# For search on localized attributes like 'name_en' via 'translations_' prefixed field
|
31
|
+
# example: <code>User.elastic_ransack({translations_name_cont: 'text'}, globalize: true)</code>
|
32
|
+
# will search on 'name_en' field (depending on current locale)
|
33
|
+
#
|
34
|
+
# === Examples
|
35
|
+
# Product.elastic_ransack(name_cont: 'alex', category_id_eq: 1, tag_ids_in: [2, 3])
|
36
|
+
# Product.elastic_ransack(tag_ids_in: '2,3,4')
|
37
|
+
# Product.elastic_ransack(created_at_gt: 1.day.ago)
|
38
|
+
# Product.elastic_ransack(q_cont: 'table')
|
39
|
+
# Product.elastic_ransack(s: 'price desc')
|
40
|
+
# Product.elastic_ransack({translations_name_cont: 'chair'}, globalize: true)
|
41
|
+
#
|
42
|
+
def elastic_ransack(options={}, search_options={})
|
43
|
+
elastic_ransack = Search.new(self, options, search_options)
|
44
|
+
elastic_ransack.search
|
45
|
+
elastic_ransack
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ElasticRansack
|
2
|
+
module Naming
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
def persisted?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_key
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_param
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_model
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Name < String
|
26
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key, :i18n_key
|
27
|
+
alias_method :cache_key, :collection
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
super('Search')
|
31
|
+
@singular = 'search'.freeze
|
32
|
+
@plural = 'searches'.freeze
|
33
|
+
@element = 'search'.freeze
|
34
|
+
@human = 'Search'.freeze
|
35
|
+
@collection = 'elastic_ransack/searches'.freeze
|
36
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
37
|
+
@param_key = 'q'.freeze
|
38
|
+
@route_key = 'searches'.freeze
|
39
|
+
@i18n_key = :elastic_ransack
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def model_name
|
45
|
+
@_model_name ||= Name.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def i18n_scope
|
49
|
+
:elastic_ransack
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ElasticRansack
|
2
|
+
class Predicate
|
3
|
+
attr_reader :name, :regexp, :query
|
4
|
+
|
5
|
+
def initialize(name, opts={})
|
6
|
+
@name = name
|
7
|
+
@regexp = opts[:regexp] || Regexp.new("^(.+)_#{@name}$")
|
8
|
+
@query = opts[:query]
|
9
|
+
raise(ArgumentError, ':query option is required') unless @query
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module ElasticRansack
|
2
|
+
class Search
|
3
|
+
include ::ElasticRansack::Naming
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :options, :search_options, :model, :search_results, :sorts, :globalize
|
7
|
+
|
8
|
+
delegate :results, :each, :empty?, :size, :[], to: :search_results
|
9
|
+
delegate :total_entries, :per_page, :total_pages, :current_page, :previous_page, :next_page, :offset, :out_of_bounds?,
|
10
|
+
to: :search_results
|
11
|
+
|
12
|
+
alias_method :klass, :model
|
13
|
+
|
14
|
+
DATETIME_REGEXP = /\d{2}\.\d{2}.\d{4} \d{2}\:\d{2}/
|
15
|
+
DATE_REGEXP = /\d{2}\.\d{2}.\d{4}/
|
16
|
+
|
17
|
+
def initialize(model, options, search_options)
|
18
|
+
search_options ||= {}
|
19
|
+
search_options.reverse_merge!(globalize: true)
|
20
|
+
@model = model
|
21
|
+
@options = options.stringify_keys
|
22
|
+
@search_options = search_options || {}
|
23
|
+
@sorts = []
|
24
|
+
@globalize = @search_options.delete(:globalize)
|
25
|
+
sorting = @options.delete('s')
|
26
|
+
if sorting.blank?
|
27
|
+
add_sort('id', 'desc')
|
28
|
+
else
|
29
|
+
sorting_split = sorting.split(/\s+/, 2)
|
30
|
+
add_sort(sorting_split[0], sorting_split[1] || 'asc')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_sort(name, dir)
|
35
|
+
@sorts << OpenStruct.new(name: name, dir: dir)
|
36
|
+
end
|
37
|
+
|
38
|
+
def search
|
39
|
+
@search_results ||= begin
|
40
|
+
that = self
|
41
|
+
query_string = []
|
42
|
+
tire.search(@search_options) do
|
43
|
+
and_filters = []
|
44
|
+
sort do
|
45
|
+
that.sorts.each do |s|
|
46
|
+
by s.name, s.dir
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
that.options.each do |k, v|
|
51
|
+
next if v.blank?
|
52
|
+
|
53
|
+
if k == 'q_cont' || k == 'q_eq'
|
54
|
+
query_string << "#{v.lucene_escape}" if v.present?
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
if k =~ /^(.+)_cont$/
|
59
|
+
attr = $1
|
60
|
+
attr = "#{$1.sub(/^translations_/, '')}_#{I18n.locale}" if that.globalize && attr =~ /^translations_(.+)/
|
61
|
+
attr_query = [
|
62
|
+
v.split.map { |part| "#{attr}:*#{part.lucene_escape}*" }.join(' AND '),
|
63
|
+
v.split.map { |part| "#{attr}:\"#{part.lucene_escape}\"" }.join(' AND ')
|
64
|
+
]
|
65
|
+
query_string << attr_query.map { |q| "(#{q})" }.join(' OR ')
|
66
|
+
next
|
67
|
+
else
|
68
|
+
v = that.format_value(v)
|
69
|
+
end
|
70
|
+
|
71
|
+
ElasticRansack.predicates.each do |predicate|
|
72
|
+
if k =~ predicate.regexp
|
73
|
+
and_filters << predicate.query.call($1, v)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
query { string query_string.join(' ') } unless query_string.blank?
|
79
|
+
filter(:and, filters: and_filters) unless and_filters.blank?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def translate(*args)
|
86
|
+
model.human_attribute_name(args.first)
|
87
|
+
end
|
88
|
+
|
89
|
+
def format_value(v)
|
90
|
+
if v =~ DATETIME_REGEXP
|
91
|
+
Date.parse(v)
|
92
|
+
elsif v =~ DATE_REGEXP
|
93
|
+
Time.parse(v)
|
94
|
+
else
|
95
|
+
v
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def method_missing(*args, &block)
|
100
|
+
@model.send(*args, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ElasticRansack do
|
4
|
+
subject { ModelSearch }
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
ModelSearch.setup_index
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'setup yields ElasticRansack' do
|
11
|
+
ElasticRansack.setup do |config|
|
12
|
+
config.should == ElasticRansack
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'add predicates' do
|
17
|
+
expect do
|
18
|
+
ElasticRansack.add_predicate 'test', {query: proc {}}
|
19
|
+
end.to change { ElasticRansack.predicates.size }.by(1)
|
20
|
+
|
21
|
+
ElasticRansack.predicates.last.name.should == 'test'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'add elastic_ransack method' do
|
25
|
+
should respond_to(:elastic_ransack)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#val_to_array' do
|
29
|
+
it 'convert comma separated string to array' do
|
30
|
+
ElasticRansack.val_to_array('2,3,4').should == [2, 3, 4]
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'return if array' do
|
34
|
+
array = [1, 2, 3]
|
35
|
+
ElasticRansack.val_to_array(array).should == array
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'return empty array if value evaluates to false' do
|
39
|
+
ElasticRansack.val_to_array(nil).should == []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'string lucene query escaping' do
|
4
|
+
it do
|
5
|
+
'string'.should respond_to(:lucene_escape)
|
6
|
+
end
|
7
|
+
|
8
|
+
%w( + - && || ! ( ) { } [ ] ^ " ~ * ? : / ).each do |char|
|
9
|
+
it "escape #{char}" do
|
10
|
+
"abc #{char} edf".lucene_escape.should == "abc \\#{char} edf"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'escape \ ' do
|
15
|
+
"abc \\ edf".lucene_escape.should == "abc \\\\ edf"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ElasticRansack::Predicate do
|
4
|
+
it 'require :query option' do
|
5
|
+
expect { ElasticRansack::Predicate.new('test', {}) }.to raise_error(ArgumentError, ':query option is required')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'creates regexp based on name' do
|
9
|
+
predicate = ElasticRansack::Predicate.new('test', {query: proc {} })
|
10
|
+
predicate.regexp.should == /^(.+)_test$/
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'predicates' do
|
4
|
+
before :all do
|
5
|
+
@model = ModelSearch
|
6
|
+
@model.setup_index
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'delegate methods to model' do
|
10
|
+
[:results, :each, :empty?, :size, :[], :total_entries, :per_page, :total_pages,
|
11
|
+
:current_page, :previous_page, :next_page, :offset, :out_of_bounds?].each do |meth|
|
12
|
+
@model.elastic_ransack.should.respond_to?(meth)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'string fields' do
|
17
|
+
it '_cont' do
|
18
|
+
@model.elastic_ransack(text_cont: 'test').map(&:id).map(&:to_i).should =~ [1, 3]
|
19
|
+
end
|
20
|
+
|
21
|
+
it '_cont localized' do
|
22
|
+
I18n.locale = :ru
|
23
|
+
@model.elastic_ransack({translations_text_cont: 'alexandr'}, {globalize: true}).map(&:id).map(&:to_i).should =~ [1]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'q_cont param search on _all fields' do
|
27
|
+
@model.elastic_ransack(q_cont: 'test').map(&:id).map(&:to_i).should =~ [1, 3]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it '_eq' do
|
32
|
+
@model.elastic_ransack(one_assoc_id_eq: 2).map(&:id).map(&:to_i).should =~ [2, 3]
|
33
|
+
end
|
34
|
+
|
35
|
+
it '_in' do
|
36
|
+
@model.elastic_ransack(many_assoc_ids_in: [3, 99]).map(&:id).map(&:to_i).should =~ [1, 2]
|
37
|
+
end
|
38
|
+
|
39
|
+
it '_in_all' do
|
40
|
+
@model.elastic_ransack(many_assoc_ids_in_all: [4, 5]).map(&:id).map(&:to_i).should =~ [2]
|
41
|
+
end
|
42
|
+
|
43
|
+
context '_gt' do
|
44
|
+
it 'integer' do
|
45
|
+
@model.elastic_ransack(int_attr_gt: 20).map(&:id).map(&:to_i).should =~ [3]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'datetime' do
|
49
|
+
@model.elastic_ransack(created_at_gt: 2.days.ago).map(&:id).map(&:to_i).should =~ [1, 3]
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'datetime string' do
|
53
|
+
@model.elastic_ransack(created_at_gt: 2.days.ago.strftime('%d.%m.%Y %H:%M')).map(&:id).map(&:to_i).should =~ [1, 3]
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'date' do
|
57
|
+
@model.elastic_ransack(created_at_gt: Date.yesterday).map(&:id).map(&:to_i).should =~ [1, 3]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'date string' do
|
61
|
+
@model.elastic_ransack(created_at_gt: Date.yesterday.strftime('%d.%m.%Y')).map(&:id).map(&:to_i).should =~ [1, 3]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context '_lt' do
|
66
|
+
it 'integer' do
|
67
|
+
@model.elastic_ransack(int_attr_lt: 20).map(&:id).map(&:to_i).should =~ [1]
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'datetime' do
|
71
|
+
@model.elastic_ransack(created_at_lt: 2.days.ago).map(&:id).map(&:to_i).should =~ [2]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'datetime string' do
|
75
|
+
@model.elastic_ransack(created_at_lt: 2.days.ago.strftime('%d.%m.%Y %H:%M')).map(&:id).map(&:to_i).should =~ [2]
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'date' do
|
79
|
+
@model.elastic_ransack(created_at_lt: Date.yesterday).map(&:id).map(&:to_i).should =~ [2]
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'date string' do
|
83
|
+
@model.elastic_ransack(created_at_lt: Date.yesterday.strftime('%d.%m.%Y')).map(&:id).map(&:to_i).should =~ [2]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it '_gteq' do
|
88
|
+
@model.elastic_ransack(int_attr_gteq: 20).map(&:id).map(&:to_i).should =~ [2, 3]
|
89
|
+
end
|
90
|
+
|
91
|
+
it '_lteq' do
|
92
|
+
@model.elastic_ransack(int_attr_lteq: 20).map(&:id).map(&:to_i).should =~ [1, 2]
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'sorting' do
|
4
|
+
before :all do
|
5
|
+
@model = ModelSearch
|
6
|
+
@model.setup_index
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'default sort by id desc' do
|
10
|
+
@model.elastic_ransack.map(&:id).map(&:to_i).should =~ [3, 2, 1]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'custom sort' do
|
14
|
+
@model.elastic_ransack(s: 'float_attr').map(&:id).map(&:to_i).should =~ [1, 3, 2]
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'custom sort mode' do
|
18
|
+
@model.elastic_ransack(s: 'float_attr desc').map(&:id).map(&:to_i).should =~ [2, 3, 1]
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'tire'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
require 'elastic_ransack'
|
5
|
+
|
6
|
+
Tire.configure do
|
7
|
+
logger 'tmp/elasticsearch.log' # Commented out logger line here so that it doesn't break specs when tmp directory doesn't exist.
|
8
|
+
url 'http://localhost:9200'
|
9
|
+
pretty 1
|
10
|
+
end
|
11
|
+
|
12
|
+
class ActiveModelBase
|
13
|
+
include ActiveModel::AttributeMethods
|
14
|
+
include ActiveModel::Serialization
|
15
|
+
include ActiveModel::Serializers::JSON
|
16
|
+
include ActiveModel::Naming
|
17
|
+
|
18
|
+
extend ActiveModel::Callbacks
|
19
|
+
define_model_callbacks :save, :destroy
|
20
|
+
|
21
|
+
attr_reader :attributes
|
22
|
+
attr_accessor :id, :created_at
|
23
|
+
|
24
|
+
def initialize(attributes = {})
|
25
|
+
@attributes = attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_indexed_json
|
29
|
+
@attributes.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(id, *args, &block)
|
33
|
+
attributes[id.to_sym] || attributes[id.to_s] || super
|
34
|
+
end
|
35
|
+
|
36
|
+
def persisted?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def save
|
41
|
+
_run_save_callbacks do
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy
|
46
|
+
_run_destroy_callbacks do
|
47
|
+
@destroyed = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroyed?
|
52
|
+
!!@destroyed
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
class ModelSearch < ActiveModelBase
|
58
|
+
include Tire::Model::Search
|
59
|
+
include Tire::Model::Callbacks
|
60
|
+
|
61
|
+
include ElasticRansack::Model
|
62
|
+
|
63
|
+
mapping do
|
64
|
+
indexes :id, type: 'integer'
|
65
|
+
indexes :int_attr, type: 'integer'
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.setup_index
|
69
|
+
tire.index.delete
|
70
|
+
tire.create_elasticsearch_index
|
71
|
+
populate
|
72
|
+
tire.index.refresh
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.test_data
|
76
|
+
[
|
77
|
+
{
|
78
|
+
id: 1, text: 'alex test', text_ru: 'alexandr',
|
79
|
+
one_assoc_id: 1, many_assoc_ids: [2, 3, 4],
|
80
|
+
int_attr: 10, float_attr: 5.5, created_at: 1.day.ago
|
81
|
+
},
|
82
|
+
{
|
83
|
+
id: 2, text: 'andrey black', text_ru: 'andrey rus',
|
84
|
+
one_assoc_id: 2, many_assoc_ids: [3, 4, 5, 6],
|
85
|
+
int_attr: 20, float_attr: 27.5, created_at: 1.month.ago
|
86
|
+
},
|
87
|
+
{
|
88
|
+
id: 3, text: 'mike test', text_ru: 'mike rus',
|
89
|
+
one_assoc_id: 2, many_assoc_ids: [10, 14, 17],
|
90
|
+
int_attr: 80, float_attr: 7.5, created_at: 1.hour.ago
|
91
|
+
}
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.populate
|
96
|
+
test_data.each do |attrs|
|
97
|
+
u = new(attrs)
|
98
|
+
u.id = attrs[:id]
|
99
|
+
u.save
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elastic_ransack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Leschenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tire
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
description: Conditions searching using predicates such as 'name_cont' or 'created_at_gt'
|
70
|
+
for elasticsearch models
|
71
|
+
email:
|
72
|
+
- leschenko.al@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- elastic_ransack.gemspec
|
83
|
+
- lib/elastic_ransack.rb
|
84
|
+
- lib/elastic_ransack/core_ext.rb
|
85
|
+
- lib/elastic_ransack/model.rb
|
86
|
+
- lib/elastic_ransack/naming.rb
|
87
|
+
- lib/elastic_ransack/predicate.rb
|
88
|
+
- lib/elastic_ransack/search.rb
|
89
|
+
- lib/elastic_ransack/version.rb
|
90
|
+
- spec/elastic_ransack/base_spec.rb
|
91
|
+
- spec/elastic_ransack/core_ext_spec.rb
|
92
|
+
- spec/elastic_ransack/predicate_spec.rb
|
93
|
+
- spec/elastic_ransack/search_spec.rb
|
94
|
+
- spec/elastic_ransack/sorting_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
homepage: https://github.com/leschenko/elastic_ransack
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.0.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Conditions searching adapter for elasticsearch models like ransack
|
120
|
+
test_files:
|
121
|
+
- spec/elastic_ransack/base_spec.rb
|
122
|
+
- spec/elastic_ransack/core_ext_spec.rb
|
123
|
+
- spec/elastic_ransack/predicate_spec.rb
|
124
|
+
- spec/elastic_ransack/search_spec.rb
|
125
|
+
- spec/elastic_ransack/sorting_spec.rb
|
126
|
+
- spec/spec_helper.rb
|