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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7c29be1f921a9f40f47975390cd2658ef90eb91
4
- data.tar.gz: 727458d961183a7e364d625085fa29dc6a7b97ee
3
+ metadata.gz: 7445f0b1bc4e4b69f743a0a5c8a613c3bda4db9e
4
+ data.tar.gz: 0ef7910bb47cef64066030952a9d876cdbf967f0
5
5
  SHA512:
6
- metadata.gz: bce0e4fccb1de8992adab38ee2b81957cf76004f9c7d3c396744c4eb81e385149aef13ac7463453aaaea8b5b15ed039f26561f70f2cdf4582009c6be8729a2af
7
- data.tar.gz: 8f2ca453649095cdca6d52dcf862e2c54dae0b3cc8cd4ebee86f985b3309d69cbdfc8ea85dd439ec617044d49317ef91379715e91d30464839888976b2b563d8
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
- gem 'ar_indexer'
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
- class CreateReverseIndices < ActiveRecord::Migration
16
- def change
17
- create_table :reverse_indices do |t|
18
- t.string :model_constant
19
- t.string :field_name
20
- t.string :word
21
- t.text :id_list
22
- end
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
- ari_configuration = {
36
- fields: [],
37
- associations: {},
38
- index_on_create: [],
39
- index_on_update: []
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
- class Post < ActiveRecord::Base
62
- has_reverse_index
63
- end
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
- class Article < ActiveRecord::Base
66
- has_reverse_index({
67
- fields: [:title, :content]
68
- })
69
- end
68
+ Now let's see some examples in the models:
70
69
 
71
- class Article < ActiveRecord::Base
72
- has_many :article_tags
73
- has_many :tags, :through => :article_tags
74
- has_reverse_index({
75
- fields: [:title, :content],
76
- associations: {
77
- tags: lambda {|object| object.tags.collect{|tag| tag.name}.join(', ')}
78
- }
79
- })
80
- end
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
- foo = IndexSearch.new([Article])
89
- # Or search multiple models
90
- # foo = IndexSearch.new([Article, List])
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
- @options = {
95
- :fields => [],
96
- # If left as an empty array, will search all fields for the given model
97
-
98
- :match => :any,
99
- # :any will expand your search string and find results that match any keyword
100
- # :all will only return results that have as many keyword matches as words in the search string
101
-
102
- :sort => :relevance,
103
- # :relevance will sort by number of keyword matches
104
- # :field allows you to specify a field to sort by
105
-
106
- :sort_method => nil,
107
- # Allows for a lambda by which to access a sortable value.
108
- # Pass a proc that takes the AR object to access a sortable value
109
- # Pass the symbol of the field name you want to access to just pull the field value
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
- foo = IndexSearch.new([Article],
123
- {
124
- :fields => [:title],
125
- :match => :all,
126
- :sort => :field,
127
- :sort_direction => :asc,
128
- :no_results_message => "Hey man, there's nothing there."
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
- foo.search('some search string')
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
- results = foo.run_search('some search string')
139
- unless results.empty?
140
- # Do stuff with your results
141
- else
142
- puts foo.no_results_message #=> Hey man, there's nothing there.
143
- end
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
+ ```
@@ -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.1"
5
- spec.date = "2015-02-05"
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.class == Proc
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
- values_to_index.delete_if {|key, value| self.ari_configuration[:associations].keys.map{|field| field.to_s}.include?(key)}
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
- values_to_index.delete_if {|key, value| self.ari_configuration[:fields].map{|field| field.to_s}.include?(key)}
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 self.class.ari_configuration[:fields].empty?
75
- self.class.columns.each do |column|
76
- if ['string', 'text'].include? column.type.to_s
77
- value = self.read_attribute(column.name)
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
- unless self.class.ari_configuration[:associations].empty?
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
- if value.class == String
100
- unless value.empty?
101
- values_to_index[association_name.to_s] = value
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
- self.index_fields(false)
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
- self.index_fields(true)
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.1
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: 2015-02-05 00:00:00.000000000 Z
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.4.5
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