ar_indexer 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +118 -98
- data/ar_indexer.gemspec +3 -3
- data/lib/ar_indexer/has_reverse_index.rb +42 -46
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7445f0b1bc4e4b69f743a0a5c8a613c3bda4db9e
|
4
|
+
data.tar.gz: 0ef7910bb47cef64066030952a9d876cdbf967f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e303a490b0f237c7c8bbe33e130b1fff20cac94d7a73ee48f26e4c3e3b9a45980a103cbd98001bd120894c2ec7ea7c7241602a21e8e300bfbf8d07f245c09dbe
|
7
|
+
data.tar.gz: e1eea6aab30a108f8adf68ca8dba8590b4b8e8b4fe4b5a4b9f429f094590aabf5e7fbd2c746ce26ee90c85cf3e5842d22c89434ee6283913a31e7325c6cbbded
|
data/README.md
CHANGED
@@ -1,43 +1,50 @@
|
|
1
|
-
#ARIndexer
|
1
|
+
# ARIndexer
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/ar_indexer.svg)](http://badge.fury.io/rb/ar_indexer)
|
4
4
|
|
5
5
|
ARIndexer provides basic indexing and text search for ActiveRecord models. You choose which fields to index per model, and the index is automatically generated/updated on create/edit.
|
6
6
|
|
7
|
-
##Installation
|
7
|
+
## Installation
|
8
8
|
|
9
9
|
Add ARIndexer to your Gemfile
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'ar_indexer'
|
13
|
+
```
|
12
14
|
|
13
15
|
Write a migration to add a reverse_indices table to the database (Rails migration generator coming soon). Due to an exception encountered in Rails 4.2 (DangerousAttributes exception), the model\_name column has been renamed to model\_constant.
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
add_index :reverse_indices, [:model_constant, :field_name, :word], :unique => true
|
24
|
-
end
|
17
|
+
```ruby
|
18
|
+
class CreateReverseIndices < ActiveRecord::Migration
|
19
|
+
def change
|
20
|
+
create_table :reverse_indices do |t|
|
21
|
+
t.string :model_constant
|
22
|
+
t.string :field_name
|
23
|
+
t.string :word
|
24
|
+
t.text :id_list
|
25
25
|
end
|
26
26
|
|
27
|
+
add_index :reverse_indices, [:model_constant, :field_name, :word], :unique => true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
27
32
|
Run `rake db:migrate`
|
28
33
|
|
29
|
-
##Usage
|
34
|
+
## Usage
|
30
35
|
|
31
|
-
###Indexing
|
36
|
+
### Indexing
|
32
37
|
|
33
38
|
Have an ActiveRecord model? Want to index some text for searching? Just add the `has_reverse_index` function to your model. Call the function with no parameters and ARIndexer will index all string and text fields. You can pass an optional hash of configuration values to customize which fields and associations are indexed, and how often each type of "field" are indexed. The default hash is below, and will be merged with the hash you pass in.
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
```ruby
|
41
|
+
ari_configuration = {
|
42
|
+
fields: [],
|
43
|
+
associations: {},
|
44
|
+
index_on_create: [],
|
45
|
+
index_on_update: []
|
46
|
+
}
|
47
|
+
```
|
41
48
|
|
42
49
|
To expand on the above configuration:
|
43
50
|
* fields: If empty, will index all String and Text fields of the AR model. Pass an array of `Symbol` field names to only index the whitelisted fields
|
@@ -46,98 +53,111 @@ To expand on the above configuration:
|
|
46
53
|
* index_on_update: If empty, will index both fields and associations as an after_update function. Add `:fields` and/or `:associations` to the array to control which are automatically indexed.
|
47
54
|
|
48
55
|
Below is an example configuration hash passed for an example `Article` model, which has a collection of `Tag` objects. In this example, we've chosen to only automatically index the fields, sometimes necessary when an AR object needs to have `reload` called on it to make sure associations are up to date. Include as many or as few options as you need.
|
49
|
-
|
50
|
-
{
|
51
|
-
fields: [:title, :subtitle, :content],
|
52
|
-
associations: {
|
53
|
-
tags: lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
|
54
|
-
},
|
55
|
-
index_on_create: [:fields],
|
56
|
-
index_on_update: [:fields]
|
57
|
-
}
|
58
|
-
|
59
|
-
Now let's see some examples in the models:
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
```ruby
|
58
|
+
{
|
59
|
+
fields: [:title, :subtitle, :content],
|
60
|
+
associations: {
|
61
|
+
tags: lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
|
62
|
+
},
|
63
|
+
index_on_create: [:fields],
|
64
|
+
index_on_update: [:fields]
|
65
|
+
}
|
66
|
+
```
|
64
67
|
|
65
|
-
|
66
|
-
has_reverse_index({
|
67
|
-
fields: [:title, :content]
|
68
|
-
})
|
69
|
-
end
|
68
|
+
Now let's see some examples in the models:
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
70
|
+
```ruby
|
71
|
+
class Post < ActiveRecord::Base
|
72
|
+
has_reverse_index
|
73
|
+
end
|
74
|
+
|
75
|
+
class Article < ActiveRecord::Base
|
76
|
+
has_reverse_index(
|
77
|
+
fields: [:title, :content]
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
class Article < ActiveRecord::Base
|
82
|
+
has_many :article_tags
|
83
|
+
has_many :tags, :through => :article_tags
|
84
|
+
has_reverse_index(
|
85
|
+
fields: [:title, :content],
|
86
|
+
associations: {
|
87
|
+
tags: lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
|
88
|
+
}
|
89
|
+
)
|
90
|
+
end
|
91
|
+
```
|
81
92
|
|
82
93
|
At this point, ARIndexer will build and maintain a reverse index for each record under these models. If you need to reindex the object at any time, the instance methods `index_object`, `index_fields`, and `index_associations` are added to all ActiveRecord objects with `has_reverse_index` declared.
|
83
94
|
|
84
|
-
###Searching
|
95
|
+
### Searching
|
85
96
|
|
86
97
|
ARIndexer also provides a simple search class for finding records by text search. To initialize an instance of this class, just pass it an array of ActiveRecord models it needs to search.
|
87
98
|
|
88
|
-
|
89
|
-
|
90
|
-
|
99
|
+
```ruby
|
100
|
+
foo = IndexSearch.new([Article])
|
101
|
+
# Or search multiple models
|
102
|
+
# foo = IndexSearch.new([Article, List])
|
103
|
+
```
|
91
104
|
|
92
105
|
You can also pass an options hash to specify what fields should be searched, how the results should be sorted, a message for displaying if there are no results, etc. The default options hash is displayed below:
|
93
106
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
:sort_direction => :desc,
|
112
|
-
# Sort order, default is DESC so that the most relevant results will be returned first
|
113
|
-
|
114
|
-
:stopwords => [],
|
115
|
-
# An array of words that should not be used in the search.
|
116
|
-
# ar_indexer has an internal array of basic stopwords, and these will be added to it
|
117
|
-
|
118
|
-
:no_results_message => 'No results were returned for the given search term.'
|
119
|
-
# A stored message that can be returned if there are no results returned
|
120
|
-
}
|
107
|
+
```ruby
|
108
|
+
@options = {
|
109
|
+
:fields => [],
|
110
|
+
# If left as an empty array, will search all fields for the given model
|
111
|
+
|
112
|
+
:match => :any,
|
113
|
+
# :any will expand your search string and find results that match any keyword
|
114
|
+
# :all will only return results that have as many keyword matches as words in the search string
|
115
|
+
|
116
|
+
:sort => :relevance,
|
117
|
+
# :relevance will sort by number of keyword matches
|
118
|
+
# :field allows you to specify a field to sort by
|
119
|
+
|
120
|
+
:sort_method => nil,
|
121
|
+
# Allows for a lambda by which to access a sortable value.
|
122
|
+
# Pass a proc that takes the AR object to access a sortable value
|
123
|
+
# Pass the symbol of the field name you want to access to just pull the field value
|
121
124
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
125
|
+
:sort_direction => :desc,
|
126
|
+
# Sort order, default is DESC so that the most relevant results will be returned first
|
127
|
+
|
128
|
+
:stopwords => [],
|
129
|
+
# An array of words that should not be used in the search.
|
130
|
+
# ar_indexer has an internal array of basic stopwords, and these will be added to it
|
131
|
+
|
132
|
+
:no_results_message => 'No results were returned for the given search term.'
|
133
|
+
# A stored message that can be returned if there are no results returned
|
134
|
+
}
|
135
|
+
|
136
|
+
|
137
|
+
foo = IndexSearch.new([Article],
|
138
|
+
{
|
139
|
+
:fields => [:title],
|
140
|
+
:match => :all,
|
141
|
+
:sort => :field,
|
142
|
+
:sort_direction => :asc,
|
143
|
+
:no_results_message => "Hey man, there's nothing there."
|
144
|
+
}
|
145
|
+
)
|
146
|
+
```
|
131
147
|
|
132
148
|
And now you're ready to search against the index that's been built.
|
133
149
|
|
134
|
-
|
150
|
+
```ruby
|
151
|
+
foo.search('some search string')
|
152
|
+
```
|
135
153
|
|
136
154
|
`foo.search` will return an array of ActiveRecord objects ordered by the number of matched terms within your search string. If no objects matched your search string, an emtpy array is returned. If no results are returned, you can request the `:no_results_message`
|
137
155
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
156
|
+
```ruby
|
157
|
+
results = foo.run_search('some search string')
|
158
|
+
unless results.empty?
|
159
|
+
# Do stuff with your results
|
160
|
+
else
|
161
|
+
puts foo.no_results_message #=> Hey man, there's nothing there.
|
162
|
+
end
|
163
|
+
```
|
data/ar_indexer.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
# Basic Gem Description
|
3
3
|
spec.name = "ar_indexer"
|
4
|
-
spec.version = "0.3.
|
5
|
-
spec.date = "
|
4
|
+
spec.version = "0.3.2"
|
5
|
+
spec.date = "2018-11-21"
|
6
6
|
spec.summary = "Allows for reverse indexing selected ActiveRecord models. Handles searching and return of objects"
|
7
7
|
spec.description = spec.summary
|
8
8
|
spec.authors = ["Josh MacLachlan"]
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.require_paths = ["lib"]
|
12
12
|
spec.files = `git ls-files`.split("\n")
|
13
13
|
spec.license = "GPL-2"
|
14
|
-
|
14
|
+
|
15
15
|
# Runtime Dependencies
|
16
16
|
spec.add_dependency('activerecord', '>= 3.0.0')
|
17
17
|
spec.add_dependency('activesupport', '>= 3.0.0')
|
@@ -5,12 +5,13 @@ module ARIndexer
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
def has_reverse_index(indexing_opts
|
8
|
+
def has_reverse_index(**indexing_opts)
|
9
9
|
if indexing_opts.nil?
|
10
10
|
indexing_opts = {}
|
11
11
|
end
|
12
12
|
|
13
13
|
fields = indexing_opts[:fields] || []
|
14
|
+
|
14
15
|
fields.each do |field_name|
|
15
16
|
unless self.columns_hash.keys.include?(field_name.to_s)
|
16
17
|
unless ['string', 'text'].include?(self.columns_hash[field_name.to_s].type.to_s)
|
@@ -20,8 +21,9 @@ module ARIndexer
|
|
20
21
|
end
|
21
22
|
|
22
23
|
associations = indexing_opts[:associations] || {}
|
24
|
+
|
23
25
|
associations.each do |association_name, access_function|
|
24
|
-
unless access_function.
|
26
|
+
unless access_function.is_a?(Proc)
|
25
27
|
raise TypeError, 'Model associations must have a Proc provided in order to reach the appropriate value.'
|
26
28
|
end
|
27
29
|
end
|
@@ -29,6 +31,7 @@ module ARIndexer
|
|
29
31
|
send :include, InstanceMethods
|
30
32
|
|
31
33
|
class_attribute :ari_configuration
|
34
|
+
|
32
35
|
self.ari_configuration = {
|
33
36
|
fields: [],
|
34
37
|
associations: {},
|
@@ -40,27 +43,29 @@ module ARIndexer
|
|
40
43
|
after_update :ar_indexer_on_update
|
41
44
|
before_destroy :ar_indexer_on_destroy
|
42
45
|
end
|
46
|
+
|
43
47
|
module_function :has_reverse_index
|
44
48
|
|
45
49
|
module InstanceMethods
|
46
50
|
def index_object(cleanup = false)
|
47
51
|
values_to_index = ar_indexer_get_indexable_values
|
52
|
+
|
48
53
|
values_to_index.each do |field_name, value|
|
49
54
|
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
50
55
|
end
|
51
56
|
end
|
52
|
-
|
57
|
+
|
53
58
|
def index_fields(cleanup = false)
|
54
|
-
values_to_index = ar_indexer_get_indexable_values
|
55
|
-
|
59
|
+
values_to_index = ar_indexer_get_indexable_values([:fields])
|
60
|
+
|
56
61
|
values_to_index.each do |field_name, value|
|
57
62
|
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
61
66
|
def index_associations(cleanup = false)
|
62
|
-
values_to_index = ar_indexer_get_indexable_values
|
63
|
-
|
67
|
+
values_to_index = ar_indexer_get_indexable_values([:associations])
|
68
|
+
|
64
69
|
values_to_index.each do |field_name, value|
|
65
70
|
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
66
71
|
end
|
@@ -68,38 +73,21 @@ module ARIndexer
|
|
68
73
|
|
69
74
|
private
|
70
75
|
|
71
|
-
def ar_indexer_get_indexable_values
|
76
|
+
def ar_indexer_get_indexable_values(index = [:fields, :associations])
|
72
77
|
values_to_index = {}
|
73
|
-
|
74
|
-
if
|
75
|
-
self.class.
|
76
|
-
|
77
|
-
|
78
|
-
if value.class == String
|
79
|
-
unless value.empty?
|
80
|
-
values_to_index[column.name] = value
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
else
|
86
|
-
self.class.ari_configuration[:fields].each do |field_name|
|
87
|
-
value = self[field_name]
|
88
|
-
if value.class == String
|
89
|
-
unless value.empty?
|
90
|
-
values_to_index[field_name.to_s] = value
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
78
|
+
|
79
|
+
if index.include?(:fields)
|
80
|
+
ari_fields = self.class.ari_configuration[:fields].map{|f| f.to_s}
|
81
|
+
indexed_columns = self.class.ari_configuration[:fields].empty? ? self.class.column_names : (self.class.column_names & ari_fields)
|
82
|
+
values_to_index.merge!(ar_indexer_gather_column_values(indexed_columns))
|
94
83
|
end
|
95
84
|
|
96
|
-
|
85
|
+
if index.include?(:associations) and !self.class.ari_configuration[:associations].empty?
|
97
86
|
self.class.ari_configuration[:associations].each do |association_name, access_function|
|
98
87
|
value = access_function.call(self)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
88
|
+
|
89
|
+
if value.is_a?(String)
|
90
|
+
values_to_index[association_name.to_s] = value unless value.empty?
|
103
91
|
end
|
104
92
|
end
|
105
93
|
end
|
@@ -107,31 +95,39 @@ module ARIndexer
|
|
107
95
|
return values_to_index
|
108
96
|
end
|
109
97
|
|
98
|
+
def ar_indexer_gather_column_values(columns)
|
99
|
+
column_values = {}
|
100
|
+
|
101
|
+
columns.each do |column|
|
102
|
+
value = self.read_attribute(column)
|
103
|
+
|
104
|
+
if value.is_a?(String)
|
105
|
+
column_values[column] = value unless value.empty?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
return column_values
|
110
|
+
end
|
111
|
+
|
110
112
|
def ar_indexer_on_create
|
111
113
|
to_index = self.class.ari_configuration[:index_on_create]
|
114
|
+
|
112
115
|
if to_index == []
|
113
116
|
self.index_object(false)
|
114
117
|
else
|
115
|
-
if to_index.include? :fields
|
116
|
-
|
117
|
-
end
|
118
|
-
if to_index.include? :associations
|
119
|
-
self.index_associations(false)
|
120
|
-
end
|
118
|
+
self.index_fields(false) if to_index.include? :fields
|
119
|
+
self.index_associations(false) if to_index.include? :associations
|
121
120
|
end
|
122
121
|
end
|
123
122
|
|
124
123
|
def ar_indexer_on_update
|
125
124
|
to_index = self.class.ari_configuration[:index_on_update]
|
125
|
+
|
126
126
|
if to_index == []
|
127
127
|
self.index_object(true)
|
128
128
|
else
|
129
|
-
if to_index.include? :fields
|
130
|
-
|
131
|
-
end
|
132
|
-
if to_index.include? :associations
|
133
|
-
self.index_associations(true)
|
134
|
-
end
|
129
|
+
self.index_fields(true) if to_index.include? :fields
|
130
|
+
self.index_associations(true) if to_index.include? :associations
|
135
131
|
end
|
136
132
|
end
|
137
133
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_indexer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh MacLachlan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
version: '0'
|
105
105
|
requirements: []
|
106
106
|
rubyforge_project:
|
107
|
-
rubygems_version: 2.
|
107
|
+
rubygems_version: 2.5.2.3
|
108
108
|
signing_key:
|
109
109
|
specification_version: 4
|
110
110
|
summary: Allows for reverse indexing selected ActiveRecord models. Handles searching
|