ar_indexer 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -14
- data/ar_indexer.gemspec +3 -3
- data/lib/ar_indexer/has_reverse_index.rb +46 -21
- data/lib/ar_indexer/index_search.rb +3 -3
- data/lib/ar_indexer/indexer.rb +8 -8
- data/lib/ar_indexer/reverse_index.rb +2 -2
- data/lib/ar_indexer.rb +2 -2
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7c29be1f921a9f40f47975390cd2658ef90eb91
|
4
|
+
data.tar.gz: 727458d961183a7e364d625085fa29dc6a7b97ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bce0e4fccb1de8992adab38ee2b81957cf76004f9c7d3c396744c4eb81e385149aef13ac7463453aaaea8b5b15ed039f26561f70f2cdf4582009c6be8729a2af
|
7
|
+
data.tar.gz: 8f2ca453649095cdca6d52dcf862e2c54dae0b3cc8cd4ebee86f985b3309d69cbdfc8ea85dd439ec617044d49317ef91379715e91d30464839888976b2b563d8
|
data/README.md
CHANGED
@@ -10,17 +10,17 @@ Add ARIndexer to your Gemfile
|
|
10
10
|
|
11
11
|
gem 'ar_indexer'
|
12
12
|
|
13
|
-
Write a migration to add a reverse_indices table to the database (Rails migration generator coming soon)
|
13
|
+
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
14
|
|
15
15
|
class CreateReverseIndices < ActiveRecord::Migration
|
16
16
|
def change
|
17
17
|
create_table :reverse_indices do |t|
|
18
|
-
t.string :
|
18
|
+
t.string :model_constant
|
19
19
|
t.string :field_name
|
20
20
|
t.string :word
|
21
21
|
t.text :id_list
|
22
22
|
end
|
23
|
-
add_index :reverse_indices, [:
|
23
|
+
add_index :reverse_indices, [:model_constant, :field_name, :word], :unique => true
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -30,30 +30,56 @@ Run `rake db:migrate`
|
|
30
30
|
|
31
31
|
###Indexing###
|
32
32
|
|
33
|
-
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
|
33
|
+
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
|
+
|
35
|
+
ari_configuration = {
|
36
|
+
fields: [],
|
37
|
+
associations: {},
|
38
|
+
index_on_create: [],
|
39
|
+
index_on_update: []
|
40
|
+
}
|
41
|
+
|
42
|
+
To expand on the above configuration:
|
43
|
+
* 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
|
44
|
+
* associations: If empty, will not index any associations. For each association to be indexed, add a `Symbol` key as the name of the association, pointing to a `lambda` which takes the object being indexed, and returns a string value.
|
45
|
+
* index_on_create: If empty, will index both fields and associations as an after_create function. Add `:fields` and/or `:associations` to the array to control which are automatically indexed.
|
46
|
+
* 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
|
+
|
48
|
+
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:
|
34
60
|
|
35
61
|
class Post < ActiveRecord::Base
|
36
62
|
has_reverse_index
|
37
63
|
end
|
38
64
|
|
39
65
|
class Article < ActiveRecord::Base
|
40
|
-
has_reverse_index(
|
66
|
+
has_reverse_index({
|
67
|
+
fields: [:title, :content]
|
68
|
+
})
|
41
69
|
end
|
42
70
|
|
43
71
|
class Article < ActiveRecord::Base
|
44
72
|
has_many :article_tags
|
45
73
|
has_many :tags, :through => :article_tags
|
46
|
-
has_reverse_index(
|
47
|
-
[
|
48
|
-
|
49
|
-
:
|
50
|
-
],
|
51
|
-
{
|
52
|
-
:tags => lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
|
74
|
+
has_reverse_index({
|
75
|
+
fields: [:title, :content],
|
76
|
+
associations: {
|
77
|
+
tags: lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
|
53
78
|
}
|
54
|
-
)
|
79
|
+
})
|
80
|
+
end
|
55
81
|
|
56
|
-
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 `
|
82
|
+
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.
|
57
83
|
|
58
84
|
###Searching###
|
59
85
|
|
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.
|
5
|
-
spec.date = "
|
4
|
+
spec.version = "0.3.1"
|
5
|
+
spec.date = "2015-02-05"
|
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"]
|
@@ -19,5 +19,5 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.add_dependency('fast-stemmer')
|
20
20
|
|
21
21
|
# Post-Install Message
|
22
|
-
spec.post_install_message = "If
|
22
|
+
spec.post_install_message = "If upgrading from v0.1.x to 0.2.x or 0.2.x to 0.3.x, be sure to read the updated documentation. Major changes were made to initialization."
|
23
23
|
end
|
@@ -5,7 +5,12 @@ module ARIndexer
|
|
5
5
|
end
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
def has_reverse_index(
|
8
|
+
def has_reverse_index(indexing_opts = {})
|
9
|
+
if indexing_opts.nil?
|
10
|
+
indexing_opts = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
fields = indexing_opts[:fields] || []
|
9
14
|
fields.each do |field_name|
|
10
15
|
unless self.columns_hash.keys.include?(field_name.to_s)
|
11
16
|
unless ['string', 'text'].include?(self.columns_hash[field_name.to_s].type.to_s)
|
@@ -14,6 +19,7 @@ module ARIndexer
|
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
22
|
+
associations = indexing_opts[:associations] || {}
|
17
23
|
associations.each do |association_name, access_function|
|
18
24
|
unless access_function.class == Proc
|
19
25
|
raise TypeError, 'Model associations must have a Proc provided in order to reach the appropriate value.'
|
@@ -22,10 +28,13 @@ module ARIndexer
|
|
22
28
|
|
23
29
|
send :include, InstanceMethods
|
24
30
|
|
25
|
-
class_attribute :
|
26
|
-
|
27
|
-
|
28
|
-
|
31
|
+
class_attribute :ari_configuration
|
32
|
+
self.ari_configuration = {
|
33
|
+
fields: [],
|
34
|
+
associations: {},
|
35
|
+
index_on_create: [],
|
36
|
+
index_on_update: []
|
37
|
+
}.merge(indexing_opts)
|
29
38
|
|
30
39
|
after_create :ar_indexer_on_create
|
31
40
|
after_update :ar_indexer_on_update
|
@@ -34,26 +43,26 @@ module ARIndexer
|
|
34
43
|
module_function :has_reverse_index
|
35
44
|
|
36
45
|
module InstanceMethods
|
37
|
-
def
|
46
|
+
def index_object(cleanup = false)
|
38
47
|
values_to_index = ar_indexer_get_indexable_values
|
39
48
|
values_to_index.each do |field_name, value|
|
40
|
-
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value,
|
49
|
+
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
41
50
|
end
|
42
51
|
end
|
43
52
|
|
44
|
-
def
|
53
|
+
def index_fields(cleanup = false)
|
45
54
|
values_to_index = ar_indexer_get_indexable_values
|
46
|
-
values_to_index.delete_if {|key, value| self.
|
55
|
+
values_to_index.delete_if {|key, value| self.ari_configuration[:associations].keys.map{|field| field.to_s}.include?(key)}
|
47
56
|
values_to_index.each do |field_name, value|
|
48
|
-
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value,
|
57
|
+
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
|
-
def
|
61
|
+
def index_associations(cleanup = false)
|
53
62
|
values_to_index = ar_indexer_get_indexable_values
|
54
|
-
values_to_index.delete_if {|key, value| self.
|
63
|
+
values_to_index.delete_if {|key, value| self.ari_configuration[:fields].map{|field| field.to_s}.include?(key)}
|
55
64
|
values_to_index.each do |field_name, value|
|
56
|
-
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value,
|
65
|
+
Indexer.index_string(self.class.to_s.split('::').last, self.id, field_name, value, cleanup)
|
57
66
|
end
|
58
67
|
end
|
59
68
|
|
@@ -62,7 +71,7 @@ module ARIndexer
|
|
62
71
|
def ar_indexer_get_indexable_values
|
63
72
|
values_to_index = {}
|
64
73
|
|
65
|
-
if self.
|
74
|
+
if self.class.ari_configuration[:fields].empty?
|
66
75
|
self.class.columns.each do |column|
|
67
76
|
if ['string', 'text'].include? column.type.to_s
|
68
77
|
value = self.read_attribute(column.name)
|
@@ -74,7 +83,7 @@ module ARIndexer
|
|
74
83
|
end
|
75
84
|
end
|
76
85
|
else
|
77
|
-
self.
|
86
|
+
self.class.ari_configuration[:fields].each do |field_name|
|
78
87
|
value = self[field_name]
|
79
88
|
if value.class == String
|
80
89
|
unless value.empty?
|
@@ -84,8 +93,8 @@ module ARIndexer
|
|
84
93
|
end
|
85
94
|
end
|
86
95
|
|
87
|
-
unless self.
|
88
|
-
self.
|
96
|
+
unless self.class.ari_configuration[:associations].empty?
|
97
|
+
self.class.ari_configuration[:associations].each do |association_name, access_function|
|
89
98
|
value = access_function.call(self)
|
90
99
|
if value.class == String
|
91
100
|
unless value.empty?
|
@@ -99,14 +108,30 @@ module ARIndexer
|
|
99
108
|
end
|
100
109
|
|
101
110
|
def ar_indexer_on_create
|
102
|
-
|
103
|
-
|
111
|
+
to_index = self.class.ari_configuration[:index_on_create]
|
112
|
+
if to_index == []
|
113
|
+
self.index_object(false)
|
114
|
+
else
|
115
|
+
if to_index.include? :fields
|
116
|
+
self.index_fields(false)
|
117
|
+
end
|
118
|
+
if to_index.include? :associations
|
119
|
+
self.index_associations(false)
|
120
|
+
end
|
104
121
|
end
|
105
122
|
end
|
106
123
|
|
107
124
|
def ar_indexer_on_update
|
108
|
-
|
109
|
-
|
125
|
+
to_index = self.class.ari_configuration[:index_on_update]
|
126
|
+
if to_index == []
|
127
|
+
self.index_object(true)
|
128
|
+
else
|
129
|
+
if to_index.include? :fields
|
130
|
+
self.index_fields(true)
|
131
|
+
end
|
132
|
+
if to_index.include? :associations
|
133
|
+
self.index_associations(true)
|
134
|
+
end
|
110
135
|
end
|
111
136
|
end
|
112
137
|
|
@@ -40,9 +40,9 @@ module ARIndexer
|
|
40
40
|
|
41
41
|
# Execute AR query based on @options[:fields]
|
42
42
|
if @options[:fields].empty?
|
43
|
-
base_results = ReverseIndex.where(:
|
43
|
+
base_results = ReverseIndex.where(:model_constant => self.search_models, :word => search_terms)
|
44
44
|
else
|
45
|
-
base_results = ReverseIndex.where(:
|
45
|
+
base_results = ReverseIndex.where(:model_constant => self.search_models, :field_name => @options[:fields], :word => search_terms)
|
46
46
|
end
|
47
47
|
|
48
48
|
unless base_results.empty?
|
@@ -80,7 +80,7 @@ module ARIndexer
|
|
80
80
|
relevancy_counts = {}
|
81
81
|
unsorted_results = []
|
82
82
|
search_models.each do |model|
|
83
|
-
model_results = base_results.where(:
|
83
|
+
model_results = base_results.where(:model_constant => model)
|
84
84
|
unless model_results.empty?
|
85
85
|
relevancy_counts[model] = {}
|
86
86
|
model_results.each do |result|
|
data/lib/ar_indexer/indexer.rb
CHANGED
@@ -19,24 +19,24 @@ module ARIndexer
|
|
19
19
|
return forward_index
|
20
20
|
end
|
21
21
|
|
22
|
-
def self.index_string(
|
22
|
+
def self.index_string(model_constant, object_id, field_name, value, repair_on_completion)
|
23
23
|
forward_index = self.break_string(value)
|
24
24
|
forward_index.each do |word|
|
25
|
-
if index_record = ReverseIndex.where(:
|
25
|
+
if index_record = ReverseIndex.where(:model_constant => model_constant, :field_name => field_name, :word => word).first
|
26
26
|
current_id_array = index_record.retrieve_id_array
|
27
27
|
unless current_id_array.include? object_id
|
28
28
|
new_id_list = (current_id_array << object_id).sort.join(',')
|
29
29
|
index_record.update(:id_list => new_id_list)
|
30
30
|
end
|
31
31
|
else
|
32
|
-
ReverseIndex.create(:
|
32
|
+
ReverseIndex.create(:model_constant => model_constant, :field_name => field_name, :word => word, :id_list => object_id)
|
33
33
|
end
|
34
34
|
end
|
35
|
-
repair_index(
|
35
|
+
repair_index(model_constant, object_id, field_name, forward_index) if repair_on_completion
|
36
36
|
end
|
37
37
|
|
38
|
-
def self.remove_index_id(
|
39
|
-
index_records = ReverseIndex.where(:
|
38
|
+
def self.remove_index_id(model_constant, object_id)
|
39
|
+
index_records = ReverseIndex.where(:model_constant => model_constant)
|
40
40
|
if index_records.count > 0
|
41
41
|
index_records.each do |record|
|
42
42
|
if record.id_list.match(/#{object_id},{0,1}/)
|
@@ -54,8 +54,8 @@ module ARIndexer
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
-
def self.repair_index(
|
58
|
-
index_records = ReverseIndex.where(:
|
57
|
+
def self.repair_index(model_constant, object_id, field_name, forward_index)
|
58
|
+
index_records = ReverseIndex.where(:model_constant => model_constant, :field_name => field_name)
|
59
59
|
if index_records.count > 0
|
60
60
|
index_records.each do |record|
|
61
61
|
if record.id_list.match(/#{object_id},{0,1}/)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module ARIndexer
|
2
2
|
class ReverseIndex < ::ActiveRecord::Base
|
3
3
|
if ::ActiveRecord::VERSION::MAJOR < 4
|
4
|
-
attr_accessible :
|
4
|
+
attr_accessible :model_constant, :field_name, :word, :id_list
|
5
5
|
end
|
6
6
|
|
7
|
-
validates_uniqueness_of :word, :scope => [:
|
7
|
+
validates_uniqueness_of :word, :scope => [:model_constant, :field_name]
|
8
8
|
|
9
9
|
def retrieve_id_array
|
10
10
|
id_array = self.id_list.split(',')
|
data/lib/ar_indexer.rb
CHANGED
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.
|
4
|
+
version: 0.3.1
|
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: 2015-02-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -87,9 +87,8 @@ homepage: https://github.com/jtmaclachlan/ar_indexer
|
|
87
87
|
licenses:
|
88
88
|
- GPL-2
|
89
89
|
metadata: {}
|
90
|
-
post_install_message: If
|
91
|
-
|
92
|
-
models.
|
90
|
+
post_install_message: If upgrading from v0.1.x to 0.2.x or 0.2.x to 0.3.x, be sure
|
91
|
+
to read the updated documentation. Major changes were made to initialization.
|
93
92
|
rdoc_options: []
|
94
93
|
require_paths:
|
95
94
|
- lib
|
@@ -105,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
104
|
version: '0'
|
106
105
|
requirements: []
|
107
106
|
rubyforge_project:
|
108
|
-
rubygems_version: 2.
|
107
|
+
rubygems_version: 2.4.5
|
109
108
|
signing_key:
|
110
109
|
specification_version: 4
|
111
110
|
summary: Allows for reverse indexing selected ActiveRecord models. Handles searching
|