customized-mongomapper-search 0.1.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGE_LOG CHANGED
@@ -1,3 +1,8 @@
1
+ 1.0.0
2
+ ------
3
+ - Major refactoring was done to modularize search. This should minimize the code that needs to be changed in the model if the modeling framework is to be switched. The search code is more generalized to accommodate Rails 3 attribute access that isn't specific to Mongo Mapper.
4
+ - Added support for indexing :many associations. This implementation is specific to Mongo Mapper but can be easily modified if the modeling framework is swapped.
5
+
1
6
  0.1.4
2
7
  ------
3
8
  - Added a way to specify the Solr document id (uniqueKey) field that the MongoMapper document _id key maps to.
data/Manifest CHANGED
@@ -2,5 +2,6 @@ CHANGE_LOG
2
2
  README.rdoc
3
3
  Rakefile
4
4
  lib/customized-mongomapper-search.rb
5
- lib/search.rb
5
+ lib/search/indexable.rb
6
+ lib/search/searchable.rb
6
7
  Manifest
data/README.rdoc CHANGED
@@ -1,37 +1,76 @@
1
1
  = tsxn-mongomapper-search
2
2
 
3
- Easily integrate mongo mapper with enterprise search like solr. MODIFIED/CUSTOMIZED to use a specified Solr URL in the model.
3
+ Easily integrate Mongo Mapper with with Solr search.
4
4
 
5
5
  == Credits
6
6
 
7
- The original gem was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.
7
+ Earlier versions < 1.0.0 were based off the mongomapper-search gem written by Fernando Meyer that can be found at http://github.com/fmeyer/mongomapper-search.
8
8
 
9
- == Usage
9
+ == Usage
10
+ == Version 1.0.0
10
11
 
11
- Within the model that includes MongoMapper::Document add the following:
12
+ To make a MongoMapper::Document indexable add the following line to include the functionality:
12
13
 
13
- class << self; attr_accessor :SOLR_URL end
14
- @SOLR_URL = "{SOLR URL}" # {SOLR URL} should be replaced with the actual solr url if that wasn't apparent
14
+ include Search::Indexable
15
15
 
16
- Example:
16
+ The Solr URL must be specified using the :solr_url method. e.g.
17
17
 
18
- class Foo
19
- include MongoMapper::Document
20
- include Search::SolrDocument
21
-
22
- class << self; attr_accessor :SOLR_URL end
23
- @SOLR_URL = "http://localhost:8080/solr/foo.core"
24
-
25
- key :name, String, :fulltext => true
26
- end
18
+ solr_url "http://localhost/solr/some.core"
27
19
 
28
- The name of the index document fields can be also be specified if the name of the keys aren't desired.
20
+ To specify which attributes that need to be indexable pass hashes with the attributes and the corresponding indexed names to the :idx_attr method. e.g.
29
21
 
30
- Example:
22
+ idx_attr :_id => "object_id", :name => "title", :foo => "bar"
31
23
 
32
- key :name, String, :fulltext => true, :solr_field_name => "object_name"
24
+ To specify which :many associations that need to be indexable pass hashes with the name of the associations and the namespace the attributes of the association should be indexed under to the :idx_assoc method. A blank namespace will result in no namespace to be pre-appended to the association attribute names. e.g.
33
25
 
34
- The name of the Solr document id/uniqueKey name can be specified that the MongoMapper document _id maps to. Within the model that includes MongoMapper::Document add the folowing:
26
+ idx_assoc :object_relations => "foo"
27
+
28
+ To add search functionality add the following line to include:
35
29
 
36
- class << self; attr_accessor :SOLR_DOC_ID_FIELD end
37
- @SOLR_DOC_ID_FIELD = "preexisting_index_id"
30
+ include Search::Searchable
31
+
32
+ == An indexing example
33
+
34
+ Define a EmbeddedDocument model and specify the indexable fields. The following model does not specify the Solr URL because it is a not a first class document.
35
+
36
+ class Project
37
+ include MongoMapper::EmbeddedDocument
38
+ include Search::Indexable
39
+
40
+ idx_attr :name => "proj_name"
41
+
42
+ key :name
43
+ end
44
+
45
+ Define a Document model and specify the indexable fields and associations.
46
+
47
+ class Employee
48
+ include MongoMapper::Document
49
+ include Search::Indexable
50
+ include Search::Searchable
51
+
52
+ solr_url "http://search-api.search.com/solr/employee.core"
53
+ idx_attr :_id => "emp_id", :name => "emp_name"
54
+ idx_assoc :projects => "emp_project"
55
+
56
+ key :name
57
+ many :projects
58
+ end
59
+
60
+ Saving an instance of Employee causes that employee to be indexed in Solr. The Lucene document that's indexed would appear something like:
61
+
62
+ employee doc
63
+ emp_id = val
64
+ emp_name = val
65
+ emp_project.proj_name = val1
66
+ emp_project.proj_name = val2
67
+ emp_project.proj_name = ....
68
+ emp_project.proj_name = valN
69
+
70
+ == A searching example
71
+
72
+ Employee.search('emp_name:"John Doe" emp_project.proj_name:"search"', :qt => 'dismax')
73
+
74
+ == Feedback & Contributions
75
+
76
+ If the documentation here is not very clear please email me any suggested improvements. Also if you would like me to pull changes from a fork to the master let me know.
data/Rakefile CHANGED
@@ -2,10 +2,10 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('customized-mongomapper-search', '0.1.4') do |p|
6
- p.description = "Easily integreate mongo mapper with enterprise search like solr. CUSTOMIZED to use a specified Solr URL in the model. Original mongomapper-search gem (version 0.1.0) was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search."
5
+ Echoe.new('customized-mongomapper-search', '1.0.0') do |p|
6
+ p.description = "Easily integreate mongo mapper with enterprise search like solr. Based off of the original mongomapper-search gem (version 0.1.0) that written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search."
7
7
  p.url = "http://github.com/tsxn26/customized-mongomapper-search"
8
- p.author = ["Fernando Meyer", "Thomas Nguyen"]
8
+ p.author = ["Thomas Nguyen"]
9
9
  p.email = "tsxn26@gmail.com"
10
10
  p.ignore_pattern = ["tmp/*", "script/*"]
11
11
  p.runtime_dependencies = ["oauth-rsolr >=0.12.1", "mongo_mapper >=0.7.0", "will_paginate >=2.3.11"]
@@ -2,27 +2,27 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{customized-mongomapper-search}
5
- s.version = "0.1.4"
5
+ s.version = "1.0.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
- s.authors = ["Fernando Meyer, Thomas Nguyen"]
9
- s.date = %q{2010-04-29}
10
- s.description = %q{Easily integreate mongo mapper with enterprise search like solr. CUSTOMIZED to use a specified Solr URL in the model. Original mongomapper-search gem (version 0.1.0) was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.}
8
+ s.authors = ["Thomas Nguyen"]
9
+ s.date = %q{2010-06-01}
10
+ s.description = %q{Easily integreate mongo mapper with enterprise search like solr. Based off of the original mongomapper-search gem (version 0.1.0) that written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.}
11
11
  s.email = %q{tsxn26@gmail.com}
12
- s.extra_rdoc_files = ["README.rdoc", "lib/customized-mongomapper-search.rb", "lib/search.rb"]
13
- s.files = ["CHANGE_LOG", "README.rdoc", "Rakefile", "lib/customized-mongomapper-search.rb", "lib/search.rb", "Manifest", "customized-mongomapper-search.gemspec"]
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/customized-mongomapper-search.rb", "lib/search/indexable.rb", "lib/search/searchable.rb"]
13
+ s.files = ["CHANGE_LOG", "README.rdoc", "Rakefile", "lib/customized-mongomapper-search.rb", "lib/search/indexable.rb", "lib/search/searchable.rb", "Manifest", "customized-mongomapper-search.gemspec"]
14
14
  s.homepage = %q{http://github.com/tsxn26/customized-mongomapper-search}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Customized-mongomapper-search", "--main", "README.rdoc"]
16
16
  s.require_paths = ["lib"]
17
17
  s.rubyforge_project = %q{customized-mongomapper-search}
18
- s.rubygems_version = %q{1.3.6}
19
- s.summary = %q{Easily integreate mongo mapper with enterprise search like solr. CUSTOMIZED to use a specified Solr URL in the model. Original mongomapper-search gem (version 0.1.0) was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Easily integreate mongo mapper with enterprise search like solr. Based off of the original mongomapper-search gem (version 0.1.0) that written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.}
20
20
 
21
21
  if s.respond_to? :specification_version then
22
22
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
23
  s.specification_version = 3
24
24
 
25
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
26
  s.add_runtime_dependency(%q<oauth-rsolr>, [">= 0.12.1"])
27
27
  s.add_runtime_dependency(%q<mongo_mapper>, [">= 0.7.0"])
28
28
  s.add_runtime_dependency(%q<will_paginate>, [">= 2.3.11"])
@@ -1,11 +1,8 @@
1
- require 'rubygems'
2
- require 'active_support'
3
- require 'mongo_mapper'
4
1
  require 'rsolr'
5
2
  require 'will_paginate/collection'
6
3
 
7
4
  module Search
8
- mattr_accessor :connection
9
- end
5
+ end
10
6
 
11
- require 'search'
7
+ require 'search/indexable'
8
+ require 'search/searchable'
@@ -0,0 +1,131 @@
1
+ module Search
2
+ module Indexable
3
+ def self.included(base)
4
+ base.class_eval do
5
+ extend ClassMethods
6
+ include InstanceMethods
7
+ end
8
+ base.after_save :index_to_solr
9
+ base.before_destroy :delete_from_solr
10
+
11
+ super
12
+ end
13
+
14
+ module ClassMethods
15
+ # Hash where keys are the attributes and the values are the name that attributes are indexed as in Solr
16
+ #
17
+ # e.g. :id => "employee_id" will have the effect where the :id attribute is indexed as "employee_id" in Solr
18
+ def idx_attr(*args)
19
+ @idx_attrs = {}
20
+ args.each { |x| @idx_attrs.merge!(x) }
21
+ end
22
+
23
+ # Hash where keys are the associations and the values are the namespace attached to the association attribute names that
24
+ # are indexed as Solr fields. If a hash value is nil or a blank string then no namespace is attached to attribute names.
25
+ #
26
+ # e.g. Employee has a one-to-many association with Project and Project has an attribute :deadline
27
+ # :projects => "awesome_project" will have the effect where the :deadline attribute is indexed as "awesome_project.deadline"
28
+ def idx_assoc(*args)
29
+ @idx_assocs = {}
30
+ args.each { |x| @idx_assocs.merge!(x) }
31
+ end
32
+
33
+ def solr_url(arg = nil)
34
+ if(arg != nil)
35
+ @solr_url = arg
36
+ end
37
+ @solr_url
38
+ end
39
+
40
+ def idx_attrs
41
+ if @idx_attrs == nil
42
+ @idx_attrs = {}
43
+ end
44
+ @idx_attrs
45
+ end
46
+
47
+ def idx_assocs
48
+ if @idx_assocs == nil
49
+ @idx_assocs = {}
50
+ end
51
+ @idx_assocs
52
+ end
53
+ end
54
+
55
+ module InstanceMethods
56
+ def index_to_solr(commit = true)
57
+ if(!self.class.solr_url.nil?) # There's no point in attempting to index if the solr url isn't specified
58
+ fields = get_indexable_fields
59
+ if(!fields.empty?) # There's no point in indexing nothing
60
+ solr_conn = RSolr.connect(:url => self.class.solr_url)
61
+ solr_conn.add(fields)
62
+ if commit
63
+ solr_conn.commit
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def delete_from_solr(commit = true)
70
+ if(!self.class.solr_url.nil?)
71
+ solr_conn = RSolr.connect(:url => self.class.solr_url)
72
+ solr_conn.delete_by_id(self._id)
73
+ if commit
74
+ solr_conn.commit
75
+ end
76
+ end
77
+ end
78
+
79
+ protected
80
+ def get_indexable_fields
81
+ fields = get_indexable_attributes
82
+ if self.respond_to?(:associations)
83
+ fields.merge!(get_indexable_associations)
84
+ end
85
+ fields
86
+ end
87
+
88
+ def get_indexable_attributes
89
+ fields = {}
90
+ if self.class.idx_attrs != nil
91
+ self.class.idx_attrs.each_pair do |x,y|
92
+ fields[y] = self.attributes[x]
93
+ end
94
+ end
95
+ fields
96
+ end
97
+
98
+ # This implementation is specific to MongoMapper
99
+ def get_indexable_associations
100
+ fields = {}
101
+ self.class.idx_assocs.each_pair do |name, namespace|
102
+ v = self.associations[name]
103
+ type = v.type
104
+ klass = Kernel.const_get(v.class_name)
105
+ namespace = namespace.blank? ? "" : namespace + "."
106
+
107
+ assocs = self.send(name)
108
+ if type == :many
109
+ assocs.each do |x|
110
+ if x.respond_to?("get_indexable_fields")
111
+ x_fields = x.get_indexable_fields
112
+ x_fields.each_pair do |field_name, field_value|
113
+ if fields[namespace + field_name.to_s] == nil
114
+ fields[namespace + field_name.to_s] = []
115
+ end
116
+ fields[namespace + field_name.to_s].push(field_value)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ # Dedupe values and get rid of nils
124
+ fields.each_pair do |x, y|
125
+ y.uniq!
126
+ y.compact!
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,41 @@
1
+ module Search
2
+ module Searchable
3
+ def self.included(base)
4
+ base.class_eval do
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ super
9
+ end
10
+
11
+ module ClassMethods
12
+ def search(query, page = 1, opts = {})
13
+ WillPaginate::Collection.create(page, 20) do |pager|
14
+ result = search_engine_query(query, opts)
15
+ if(result.class == Array)
16
+ result.compact!
17
+ end
18
+ pager.replace(result)
19
+ pager.total_entries = result.length
20
+ end
21
+ end
22
+
23
+ protected
24
+ def search_engine_query(query, opts = {})
25
+ opts.reverse_merge!(:limit => 20, :offset => 0)
26
+ solr_conn = RSolr.connect(:url => self.solr_url)
27
+ resp = solr_conn.select(:q => query, :rows => opts[:limit], :start => opts[:offset], :qt => !opts[:qt].blank? ? opts[:qt] : "standard", :fl => '*,score')['response']
28
+
29
+ id_name = self.idx_attrs[:_id].to_s
30
+
31
+ return resp['docs'].map do |doc|
32
+ id = doc[id_name]
33
+ if id.class == Array
34
+ id = id[0]
35
+ end
36
+ find(id)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,29 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: customized-mongomapper-search
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 23
4
5
  prerelease: false
5
6
  segments:
6
- - 0
7
7
  - 1
8
- - 4
9
- version: 0.1.4
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
10
11
  platform: ruby
11
12
  authors:
12
- - Fernando Meyer, Thomas Nguyen
13
+ - Thomas Nguyen
13
14
  autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-04-29 00:00:00 -07:00
18
+ date: 2010-06-01 00:00:00 -07:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: oauth-rsolr
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 45
27
30
  segments:
28
31
  - 0
29
32
  - 12
@@ -35,9 +38,11 @@ dependencies:
35
38
  name: mongo_mapper
36
39
  prerelease: false
37
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
38
42
  requirements:
39
43
  - - ">="
40
44
  - !ruby/object:Gem::Version
45
+ hash: 3
41
46
  segments:
42
47
  - 0
43
48
  - 7
@@ -49,9 +54,11 @@ dependencies:
49
54
  name: will_paginate
50
55
  prerelease: false
51
56
  requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
52
58
  requirements:
53
59
  - - ">="
54
60
  - !ruby/object:Gem::Version
61
+ hash: 21
55
62
  segments:
56
63
  - 2
57
64
  - 3
@@ -59,7 +66,7 @@ dependencies:
59
66
  version: 2.3.11
60
67
  type: :runtime
61
68
  version_requirements: *id003
62
- description: Easily integreate mongo mapper with enterprise search like solr. CUSTOMIZED to use a specified Solr URL in the model. Original mongomapper-search gem (version 0.1.0) was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.
69
+ description: Easily integreate mongo mapper with enterprise search like solr. Based off of the original mongomapper-search gem (version 0.1.0) that written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.
63
70
  email: tsxn26@gmail.com
64
71
  executables: []
65
72
 
@@ -68,13 +75,15 @@ extensions: []
68
75
  extra_rdoc_files:
69
76
  - README.rdoc
70
77
  - lib/customized-mongomapper-search.rb
71
- - lib/search.rb
78
+ - lib/search/indexable.rb
79
+ - lib/search/searchable.rb
72
80
  files:
73
81
  - CHANGE_LOG
74
82
  - README.rdoc
75
83
  - Rakefile
76
84
  - lib/customized-mongomapper-search.rb
77
- - lib/search.rb
85
+ - lib/search/indexable.rb
86
+ - lib/search/searchable.rb
78
87
  - Manifest
79
88
  - customized-mongomapper-search.gemspec
80
89
  has_rdoc: true
@@ -92,16 +101,20 @@ rdoc_options:
92
101
  require_paths:
93
102
  - lib
94
103
  required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
95
105
  requirements:
96
106
  - - ">="
97
107
  - !ruby/object:Gem::Version
108
+ hash: 3
98
109
  segments:
99
110
  - 0
100
111
  version: "0"
101
112
  required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
102
114
  requirements:
103
115
  - - ">="
104
116
  - !ruby/object:Gem::Version
117
+ hash: 11
105
118
  segments:
106
119
  - 1
107
120
  - 2
@@ -109,9 +122,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
122
  requirements: []
110
123
 
111
124
  rubyforge_project: customized-mongomapper-search
112
- rubygems_version: 1.3.6
125
+ rubygems_version: 1.3.7
113
126
  signing_key:
114
127
  specification_version: 3
115
- summary: Easily integreate mongo mapper with enterprise search like solr. CUSTOMIZED to use a specified Solr URL in the model. Original mongomapper-search gem (version 0.1.0) was written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.
128
+ summary: Easily integreate mongo mapper with enterprise search like solr. Based off of the original mongomapper-search gem (version 0.1.0) that written by Fernando Meyer and can be found at http://github.com/fmeyer/mongomapper-search.
116
129
  test_files: []
117
130
 
data/lib/search.rb DELETED
@@ -1,86 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- module Search::SolrDocument
4
- module ClassMethods
5
- def init_connection
6
- @Solr ||= RSolr.connect(:url => instance_variable_get(:@SOLR_URL))
7
- end
8
-
9
- def show_url
10
- @test ||= instance_variable_get(:@SOLR_URL)
11
- end
12
-
13
- def search(query, page = 1, opts = {})
14
- init_connection()
15
- WillPaginate::Collection.create(page, 20) do |pager|
16
- result = search_engine_query(query, opts)
17
- if(result.class == Array)
18
- result.compact!
19
- end
20
- pager.replace(result)
21
- pager.total_entries = result.length
22
- end
23
- end
24
-
25
- protected
26
- def search_engine_query(query, opts = {})
27
- opts.reverse_merge!(:limit => 20, :offset => 0)
28
- resp = @Solr.select(:q => query, :rows => opts[:limit], :start => opts[:offset], :qt => !opts[:qt].blank? ? opts[:qt] : "standard", :fl => '*,score')['response']
29
-
30
- if(instance_variable_defined?(:@SOLR_DOC_ID_FIELD) && !instance_variable_get(:@SOLR_DOC_ID_FIELD).blank?)
31
- id_name = instance_variable_get(:@SOLR_DOC_ID_FIELD)
32
- else
33
- id_name = 'id'
34
- end
35
-
36
- return resp['docs'].map do |doc|
37
- id = doc[id_name]
38
- if id.class == Array
39
- id = id[0]
40
- end
41
- find(id)
42
- end
43
- end
44
- end
45
-
46
- module InstanceMethods
47
- def to_index
48
- if(self.class.instance_variable_defined?(:@SOLR_DOC_ID_FIELD) && !self.class.SOLR_DOC_ID_FIELD.blank?)
49
- attrs = {self.class.SOLR_DOC_ID_FIELD => self._id}
50
- else
51
- attrs = {'id' => self._id}
52
- end
53
-
54
- self.keys.each_pair do |name, key|
55
- field_name = key.options[:solr_field_name]
56
- field_name ||= name
57
- attrs.merge!(field_name => self[name]) if key.options[:fulltext] && self[name] != nil
58
- end
59
- attrs
60
- end
61
-
62
- def init_connection
63
- @Solr ||= RSolr.connect(:url => self.class.SOLR_URL)
64
- end
65
-
66
- protected
67
- def save_on_search_engine
68
- init_connection
69
- @Solr.add(to_index)
70
- @Solr.commit
71
- end
72
-
73
- def delete_from_search_engine
74
- init_connection
75
- @Solr.delete_by_id(self._id)
76
- @Solr.commit
77
- end
78
- end
79
-
80
- def self.included(receiver)
81
- receiver.extend ClassMethods
82
- receiver.send :include, InstanceMethods
83
- receiver.after_save :save_on_search_engine
84
- receiver.before_destroy :delete_from_search_engine
85
- end
86
- end