quick-search 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c85c4ec9923d45730874abd5486c67ee2728c8f5
4
+ data.tar.gz: d9595451eee2afb8452c3d007f1174e48954d982
5
+ SHA512:
6
+ metadata.gz: 849acbebb9459e92596fd8db44066a1f6e45c88a2a44dbc6a1a50c759d74a6a155653cd99729d8b7aa3b59652b13c63ea98354dd49db8c78d820fd6aff65375d
7
+ data.tar.gz: 690a11fadc716191ee7960d2241b883590ad6c3cab504050cc0d27537f4b162b0e9a167d70a448eeda87fb4812d68df021fb23b73cc928fb0fed23bf0364294a
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .ruby-*
4
+ .idea/
5
+ Gemfile.lock
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in quick-search.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Elementar Sistemas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # QuickSearch
2
+
3
+ A quick search concern for ActiveRecord and Mongoid models.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'quick-search'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install quick-search
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( http://github.com/elementar/quick-search/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,2 @@
1
+ require 'quick-search/version'
2
+ require 'quick-search/search'
@@ -0,0 +1,48 @@
1
+ module QuickSearch
2
+ module Adapters
3
+ class ActiveRecordAdapter
4
+ def initialize(cls)
5
+ @cls = cls
6
+ end
7
+
8
+ def make_clauses_for_token(s, token, fields)
9
+ s = s.joins calculate_needed_joins(fields)
10
+ s = s.where build_parameterized_condition(fields, :s),
11
+ s: "%#{token}%"
12
+ s
13
+ end
14
+
15
+ def default_quick_search_fields
16
+ @cls.columns.select { |c| c.type == :string }.map(&:name)
17
+ end
18
+
19
+ private
20
+
21
+ def calculate_needed_joins(a, stack = [], &block)
22
+ case a
23
+ when Hash
24
+ a.map { |k, v| calculate_needed_joins(v, stack + [k], &block) }.compact
25
+ when Array
26
+ a.map { |v| calculate_needed_joins(v, stack, &block) }.compact
27
+ when String, Symbol
28
+ stack.reverse.reduce(nil) { |h, v| h && { v => h } || v }
29
+ else
30
+ raise "Unrecognized input: #{a.inspect} (#{a.class.name})"
31
+ end
32
+ end
33
+
34
+ def build_parameterized_condition(f, n, cls = @cls)
35
+ case f
36
+ when Hash
37
+ f.map { |k, v| build_parameterized_condition(v, n, cls.reflect_on_association(k).klass) } * ' or '
38
+ when Array
39
+ f.map { |ff| build_parameterized_condition(ff, n, cls) } * ' or '
40
+ when String, Symbol
41
+ "`#{cls.table_name}`.`#{f}` like :#{n}"
42
+ else
43
+ raise "Unrecognized input: #{f.inspect} (#{f.class.name})"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ module QuickSearch
2
+ module Adapters
3
+ class MongoidAdapter
4
+ def initialize(cls)
5
+ @cls = cls
6
+ end
7
+
8
+ def make_clauses_for_token(s, token, fields)
9
+ s.and '$or' => fields.map { |f| { to_field_name(f) => /#{Regexp.escape token}/i } }
10
+ end
11
+
12
+ def default_quick_search_fields
13
+ @cls.fields.select { |_, f| f.type == String }.map(&:first)
14
+ end
15
+
16
+ private
17
+
18
+ def to_field_name(f)
19
+ case f
20
+ when Hash
21
+ head = f.first
22
+ "#{head.first}.#{to_field_name(head.last)}"
23
+ when String, Symbol
24
+ f
25
+ else
26
+ raise "Unrecognized input: #{f}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,79 @@
1
+ module QuickSearch
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ # Defines the fields that will be available to the quick search.
8
+ # If a hash is used on ActiveRecord, an +inner join+ will be made.
9
+ def quick_search_fields(*fields)
10
+ @quick_search_fields = fields
11
+ end
12
+
13
+ # Adds an expression to the quick search. Several expressions can be added, but only the first one
14
+ # that matches the search token will be ran.
15
+ # @param [Regexp] rx A regular expression, in which to test the quick search tokens.
16
+ # @param [Proc] proc The proc to run when the token matches. It will receive the +MatchData+ as parameter.
17
+ def quick_search_expression(rx, proc)
18
+ (@quick_search_expressions ||= {})[rx] = proc
19
+ end
20
+
21
+ # Performs a quick search. Returns the used tokens in the second parameter.
22
+ def quick_search(search, tokens_array = [])
23
+ adapter # fail fast, if the adapter can not be created
24
+
25
+ relation = all
26
+ (search || '').split(/\s+/).each do |token|
27
+ next unless token.present?
28
+
29
+ if exprs = eval_expressions(relation, token)
30
+ relation = exprs
31
+ next
32
+ end
33
+
34
+ @quick_search_fields ||= adapter.default_quick_search_fields
35
+ tokens_array << token
36
+
37
+ relation = adapter.make_clauses_for_token(relation, token, @quick_search_fields)
38
+ end
39
+ relation
40
+ end
41
+
42
+ private
43
+
44
+ def adapter
45
+ @adapter ||= begin
46
+ if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
47
+ require 'quick-search/adapters/active_record_adapter'
48
+ Adapters::ActiveRecordAdapter.new(self)
49
+ elsif defined?(Mongoid) && defined?(Mongoid::Document) && self < Mongoid::Document
50
+ require 'quick-search/adapters/mongoid_adapter'
51
+ Adapters::MongoidAdapter.new(self)
52
+ else
53
+ raise UnsupportedAdapter.new self.name
54
+ end
55
+ end
56
+ end
57
+
58
+ # Evaluates the expressions defined by #quick_search_expression.
59
+ # @return [Object] the new query, or +nil+ if there's no expression matching the token.
60
+ def eval_expressions(s, token)
61
+ return nil if @quick_search_expressions.blank?
62
+ @quick_search_expressions.each do |rx, proc|
63
+ if m = (/\A(?:#{rx})\z/.match(token))
64
+ s = s.instance_exec(m, &proc)
65
+ return s
66
+ end
67
+ end
68
+ nil
69
+ end
70
+ end
71
+
72
+ class UnsupportedAdapter < RuntimeError
73
+ DEFAULT_MESSAGE = 'QuickSearch could not find an adapter for your class: %s'
74
+
75
+ def initialize(cls, *args)
76
+ super(sprintf(DEFAULT_MESSAGE, cls), *args)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,5 @@
1
+ module Quick
2
+ module Search
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'quick-search/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'quick-search'
8
+ spec.version = Quick::Search::VERSION
9
+ spec.authors = ['Fábio D. Batista']
10
+ spec.email = ['fabio.david.batista@gmail.com']
11
+ spec.summary = %q{A quick search concern for ActiveRecord and Mongoid models}
12
+ spec.description = %q{This gem was extracted from Elementar projects.}
13
+ spec.homepage = 'https://github.com/elementar/quick-search'
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 = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '< 2.0'
22
+ spec.add_development_dependency 'rake', '~> 10'
23
+ spec.add_development_dependency 'rspec', '~> 3.1'
24
+
25
+ spec.add_development_dependency 'activerecord', '~> 4.0'
26
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
27
+
28
+ spec.add_development_dependency 'mongoid', '~> 4.0'
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'models/active_record_test_models'
2
+
3
+ module ActiveRecordTestModels
4
+ RSpec.describe 'when using the ActiveRecord adapter' do
5
+ it 'should perform simple searches' do
6
+ expect(SimpleUser.quick_search('john')).not_to be_empty
7
+ expect(SimpleUser.quick_search('waldi')).to be_empty
8
+ end
9
+ it 'should perform joined searches' do
10
+ expect(UserConfiguredWithJoins.quick_search('waldi')).not_to be_empty
11
+ end
12
+ it 'joined searches should not return duplicates', :pending do
13
+ expect(UserConfiguredWithJoins.quick_search('a').size).to eq 1
14
+ end
15
+ end
16
+ end
data/spec/both_spec.rb ADDED
File without changes
@@ -0,0 +1,45 @@
1
+ require 'active_record'
2
+ require 'quick-search'
3
+
4
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
5
+ ActiveRecord::Schema.define do
6
+ self.verbose = false
7
+
8
+ create_table :users, force: true do |t|
9
+ t.string :name
10
+ t.integer :age
11
+ t.timestamps
12
+ end
13
+
14
+ create_table :pets, force: true do |t|
15
+ t.references :user
16
+ t.string :name
17
+ t.integer :age
18
+ t.timestamps
19
+ end
20
+ end
21
+
22
+ module ActiveRecordTestModels
23
+ class SimpleUser < ActiveRecord::Base
24
+ self.table_name = 'users'
25
+ include QuickSearch
26
+
27
+ has_many :pets, foreign_key: 'user_id'
28
+ end
29
+
30
+ class UserConfiguredWithJoins < ActiveRecord::Base
31
+ self.table_name = 'users'
32
+ include QuickSearch
33
+
34
+ quick_search_fields :name, pets: :name
35
+
36
+ has_many :pets, foreign_key: 'user_id'
37
+ end
38
+
39
+ class Pet < ActiveRecord::Base
40
+ end
41
+
42
+ SimpleUser.create! name: 'John' do |u|
43
+ u.pets.build [{ name: 'Schatzi', age: 3 }, { name: 'Blume', age: 2 }, { name: 'Waldi', age: 1 }]
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ hosts:
5
+ - localhost
6
+ database: quick-search-tests
@@ -0,0 +1,59 @@
1
+ require 'mongoid'
2
+ require 'quick-search'
3
+
4
+ module MongoidTestModels
5
+ class SimpleUser
6
+ include Mongoid::Document
7
+ include QuickSearch
8
+
9
+ embeds_many :pets, class_name: 'MongoidTestModels::SimplePet'
10
+
11
+ field :name, type: String
12
+ field :age, type: Integer
13
+ end
14
+
15
+ class SimplePet
16
+ include Mongoid::Document
17
+
18
+ embedded_in :simple_user
19
+
20
+ field :name, type: String
21
+ field :age, type: Integer
22
+ end
23
+
24
+ class UserConfiguredForSubdocument
25
+ include Mongoid::Document
26
+ include QuickSearch
27
+
28
+ embeds_many :pets, class_name: 'MongoidTestModels::PetConfiguredForSubdocument'
29
+
30
+ field :name, type: String
31
+ field :age, type: Integer
32
+
33
+ quick_search_fields :name, pets: :name
34
+ end
35
+
36
+ class PetConfiguredForSubdocument
37
+ include Mongoid::Document
38
+
39
+ embedded_in :user_configured_for_subdocument
40
+
41
+ field :name, type: String
42
+ field :age, type: Integer
43
+ end
44
+
45
+ Mongoid.load! 'spec/models/mongoid.yml', :test
46
+ Mongoid::Config.purge!
47
+
48
+ SimpleUser.create! name: 'John' do |u|
49
+ u.pets.build name: 'Schatzi', age: 3
50
+ u.pets.build name: 'Blume', age: 2
51
+ u.pets.build name: 'Waldi', age: 1
52
+ end
53
+
54
+ UserConfiguredForSubdocument.create! name: 'John' do |u|
55
+ u.pets.build name: 'Schatzi', age: 3
56
+ u.pets.build name: 'Blume', age: 2
57
+ u.pets.build name: 'Waldi', age: 1
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ require 'models/mongoid_test_models'
2
+
3
+ module MongoidTestModels
4
+ RSpec.describe 'when using the Mongoid adapter' do
5
+ it 'should perform simple searches' do
6
+ expect(SimpleUser.quick_search('john')).not_to be_empty
7
+ expect(SimpleUser.quick_search('waldi')).to be_empty
8
+ end
9
+ it 'should perform searches on subdocuments' do
10
+ expect(UserConfiguredForSubdocument.quick_search('waldi')).not_to be_empty
11
+ end
12
+ it 'searches on subdocuments should not return duplicates (duh)' do
13
+ expect(UserConfiguredForSubdocument.quick_search('a').size).to eq 1
14
+ end
15
+ end
16
+ end
data/spec/none_spec.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'quick-search'
2
+
3
+ RSpec.describe 'the method is called on an unsupported record class' do
4
+ class PlainModel
5
+ include QuickSearch
6
+ end
7
+
8
+ subject { PlainModel }
9
+
10
+ it 'should raise an exception' do
11
+ expect {
12
+ subject.quick_search 'the quick brown fox'
13
+ }.to raise_error QuickSearch::UnsupportedAdapter, /could not find an adapter for your class: PlainModel/
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ RSpec.configure do |config|
2
+ config.disable_monkey_patching!
3
+
4
+ if config.files_to_run.one?
5
+ config.default_formatter = 'doc'
6
+ end
7
+
8
+ # Run specs in random order to surface order dependencies. If you find an
9
+ # order dependency and want to debug it, you can fix the order by providing
10
+ # the seed, which is printed after each run.
11
+ # --seed 1234
12
+ config.order = :random
13
+
14
+ # Seed global randomization in this process using the `--seed` CLI option.
15
+ # Setting this allows you to use `--seed` to deterministically reproduce
16
+ # test failures related to randomization by passing the same `--seed` value
17
+ # as the one that triggered the failure.
18
+ Kernel.srand config.seed
19
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quick-search
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Fábio D. Batista
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.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: '10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activerecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mongoid
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
97
+ description: This gem was extracted from Elementar projects.
98
+ email:
99
+ - fabio.david.batista@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - lib/quick-search.rb
111
+ - lib/quick-search/adapters/active_record_adapter.rb
112
+ - lib/quick-search/adapters/mongoid_adapter.rb
113
+ - lib/quick-search/search.rb
114
+ - lib/quick-search/version.rb
115
+ - quick-search.gemspec
116
+ - spec/active_record_spec.rb
117
+ - spec/both_spec.rb
118
+ - spec/models/active_record_test_models.rb
119
+ - spec/models/mongoid.yml
120
+ - spec/models/mongoid_test_models.rb
121
+ - spec/mongoid_spec.rb
122
+ - spec/none_spec.rb
123
+ - spec/spec_helper.rb
124
+ homepage: https://github.com/elementar/quick-search
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.2.2
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: A quick search concern for ActiveRecord and Mongoid models
148
+ test_files:
149
+ - spec/active_record_spec.rb
150
+ - spec/both_spec.rb
151
+ - spec/models/active_record_test_models.rb
152
+ - spec/models/mongoid.yml
153
+ - spec/models/mongoid_test_models.rb
154
+ - spec/mongoid_spec.rb
155
+ - spec/none_spec.rb
156
+ - spec/spec_helper.rb