rmla 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ require 'mebla'
2
+ require 'rails'
3
+
4
+ # A wrapper for slingshot elastic-search adapter for Mongoid
5
+ module Mebla
6
+ # @private
7
+ # Railtie for Mebla
8
+ class Railtie < Rails::Railtie
9
+ # Configuration
10
+ initializer "mebla.set_configs" do |app|
11
+ Mebla.configure do |config|
12
+ # Open logfile
13
+ config.logger = ActiveSupport::BufferedLogger.new(
14
+ open("#{Dir.pwd}/log/#{Rails.env}.mebla.log", "a")
15
+ )
16
+ # Setup the log level
17
+ config.logger.level = case app.config.log_level
18
+ when :info
19
+ ActiveSupport::BufferedLogger::Severity::INFO
20
+ when :warn
21
+ ActiveSupport::BufferedLogger::Severity::WARN
22
+ when :error
23
+ ActiveSupport::BufferedLogger::Severity::ERROR
24
+ when :fatal
25
+ ActiveSupport::BufferedLogger::Severity::FATAL
26
+ else
27
+ ActiveSupport::BufferedLogger::Severity::DEBUG
28
+ end
29
+
30
+ config.setup_logger
31
+ end
32
+ end
33
+
34
+ # Rake tasks
35
+ rake_tasks do
36
+ load File.expand_path('../tasks.rb', __FILE__)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,91 @@
1
+ # A wrapper for slingshot elastic-search adapter for Mongoid
2
+ module Mebla
3
+ # Represents a set of search results
4
+ class ResultSet
5
+ include Enumerable
6
+ attr_reader :entries, :facets, :time, :total
7
+
8
+ # --
9
+ # OPTIMIZE: needs major refractoring
10
+ # ++
11
+
12
+ # Creates a new result set from an elasticsearch response hash
13
+ # @param response
14
+ def initialize(response)
15
+ # Keep the query time
16
+ @time = response['took']
17
+ # Keep the facets
18
+ @facets = response['facets']
19
+ # Keep the query total to check against the count
20
+ @total = response['hits']['total']
21
+
22
+ # Be efficient only query the database once
23
+ model_ids = []
24
+
25
+ # Collect results' ids
26
+ response['hits']['hits'].each do |hit|
27
+ model_class = hit['_type'].camelize.constantize
28
+ model_ids << [model_class]
29
+
30
+ if model_class.embedded?
31
+ model_class_collection = model_ids.assoc(model_class)
32
+ # collect parent ids
33
+ # [class, [parent_id, ids]]
34
+ parent_id = hit['_source']['_parent']
35
+
36
+ model_class_collection << [parent_id]
37
+
38
+ model_class_collection.assoc(parent_id) << hit['_source']['id']
39
+ else
40
+ # collect ids
41
+ # [class, ids]
42
+ model_ids.assoc(model_class) << hit['_source']['id']
43
+ end
44
+ end
45
+
46
+ # Cast the results into their appropriate classes
47
+ @entries = []
48
+
49
+ model_ids.each do |model_class_collection|
50
+ model_class = model_class_collection.first
51
+ ids = model_class_collection.drop(1)
52
+
53
+ unless model_class.embedded?
54
+ # Retrieve the results from the database
55
+ ids.each do |id|
56
+ @entries << model_class.find(id)
57
+ end
58
+ else
59
+ # Get the parent
60
+ parent_class = model_class.embedded_parent
61
+ access_method = model_class.embedded_as
62
+
63
+ ids.each do |parent_id_collection|
64
+ parent_id = parent_id_collection.first
65
+ entries_ids = parent_id_collection.drop(1)
66
+
67
+ parent = parent_class.find parent_id
68
+
69
+ # Retrieve the results from the database
70
+ entries_ids.each do |entry_id|
71
+ @entries << parent.send(access_method.to_sym).find(entry_id)
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ Mebla.log("WARNING: Index not synchronized with the database; index total hits: #{@total}, retrieved documents: #{self.count}", :warn) if @total != self.count
78
+ end
79
+
80
+ # Iterates over the collection
81
+ def each(&block)
82
+ @entries.each(&block)
83
+ end
84
+
85
+ # Returns the item with the given index
86
+ # @param [Integer] index
87
+ def [](index)
88
+ @entries[index]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,240 @@
1
+ # A wrapper for slingshot elastic-search adapter for Mongoid
2
+ module Mebla
3
+ # Handles all searching functions and chains search to define filters, sorting or facets.
4
+ #
5
+ # This example searches posts by tags, sorts and filters the results::
6
+ #
7
+ # criteria = Post.search.terms(:tags, ['ruby', 'rails']).ascending(:publish_date).only(:author => ['cousine'])
8
+ #
9
+ # This will search the index for posts tagged 'ruby' or 'rails', arrange the results ascendingly according
10
+ # to their publish dates, and filter the results by the author named 'cousine'.
11
+ #
12
+ # The search won't be executed unless we try accessing the results collection::
13
+ #
14
+ # results = criteria.hits
15
+ #
16
+ # Or directly iterate the collection::
17
+ #
18
+ # criteria.each do |result|
19
+ # ...
20
+ # end
21
+ #
22
+ # Mebla supports multiple methods of searching
23
+ #
24
+ # You can either search by direct Lucene query::
25
+ #
26
+ # Mebla.search("query")
27
+ #
28
+ # Or by term::
29
+ #
30
+ # Mebla.search.term(:field, "term")
31
+ #
32
+ # Or by terms::
33
+ #
34
+ # Mebla.search.terms(:field, ["term 1", "term 2", ...])
35
+ class Search
36
+ include Enumerable
37
+ attr_reader :slingshot_search, :results
38
+
39
+ # Creates a new Search object
40
+ # @param [String] query_string optional search query
41
+ # @param [String, Symbol, Array] type_names a string, symbol or array representing the models to be searcheds
42
+ def initialize(query_string = "", type_names = [])
43
+ # Convert type names from string or symbol to array
44
+ type_names = case true
45
+ when type_names.is_a?(Symbol), type_names.is_a?(String)
46
+ [type_names]
47
+ when type_names.is_a?(Array) && !type_names.empty?
48
+ type_names.collect{|name| name.to_s}
49
+ else
50
+ []
51
+ end
52
+
53
+ @slingshot_search = Slingshot::Search::Search.new(Mebla.context.slingshot_index_name, {})
54
+ # Add a type filter to return only certain types
55
+ unless type_names.empty?
56
+ only(:_type => type_names)
57
+ end
58
+
59
+ unless query_string.blank?
60
+ query(query_string)
61
+ end
62
+ end
63
+
64
+ # Creates a terms search criteria
65
+ # @param [String, Symbol] field the field to search
66
+ # @param [Array] values the terms to match
67
+ # @param [Hash] options to refine the search
68
+ # @return [Mebla::Search]
69
+ #
70
+ # Match Posts tagged with either 'ruby' or 'rails'::
71
+ #
72
+ # Post.search.terms(:tags, ['ruby', 'rails'], :minimum_match => 1)
73
+ def terms(field, values, options = {})
74
+ @slingshot_search = @slingshot_search.query
75
+ @slingshot_search.instance_variable_get(:@query).terms(field, values, options)
76
+ self
77
+ end
78
+
79
+ # Creates a term search criteria
80
+ # @param [String, Symbol] field the field to search
81
+ # @param [String] value term to match
82
+ # @return [Mebla::Search]
83
+ def term(field, value)
84
+ @slingshot_search = @slingshot_search.query
85
+ @slingshot_search.instance_variable_get(:@query).term(field, value)
86
+ self
87
+ end
88
+
89
+ # Creates a Lucene query string search criteria
90
+ # @param [String] query_string search query
91
+ # @param [Hash] options to refine the search
92
+ #
93
+ # Match Posts with "Test Lucene query" as title::
94
+ #
95
+ # Post.search.query("Test Lucene query", :default_field => "title")
96
+ #
97
+ # You can also instead::
98
+ #
99
+ # Post.search.query("title: Test Lucene query")
100
+ #
101
+ # Or to search all fields::
102
+ #
103
+ # Post.search.query("Test Lucene query")
104
+ #
105
+ # @note For more information check {http://lucene.apache.org/java/2_4_0/queryparsersyntax.html Lucene's query syntax}
106
+ def query(query_string, options = {})
107
+ @slingshot_search = @slingshot_search.query
108
+ @slingshot_search.instance_variable_get(:@query).string(query_string, options)
109
+ self
110
+ end
111
+
112
+ # Sorts results ascendingly
113
+ # @param [String, Symbol] field to sort by
114
+ # @return [Mebla::Search]
115
+ def ascending(field)
116
+ @slingshot_search = @slingshot_search.sort
117
+ @slingshot_search.instance_variable_get(:@sort).send(field.to_sym, 'asc')
118
+ self
119
+ end
120
+
121
+ # Sorts results descendingly
122
+ # @param [String, Symbol] field to sort by
123
+ # @return [Mebla::Search]
124
+ def descending(field)
125
+ @slingshot_search = @slingshot_search.sort
126
+ @slingshot_search.instance_variable_get(:@sort).send(field.to_sym, 'desc')
127
+ self
128
+ end
129
+
130
+ # Creates a new facet for the search
131
+ # @param [String] name of the facet
132
+ # @param [String, Symbol] field to create a facet for
133
+ # @param [Hash] options
134
+ # @return [Mebla::Search]
135
+ #
136
+ # Defining a global facet named "tags"::
137
+ #
138
+ # Post.search("*").facet("tags", :tag, :global => true)
139
+ #
140
+ # @note check {http://www.elasticsearch.org/guide/reference/api/search/facets/ elasticsearch's facet reference} for more information
141
+ def facet(name, field, options={})
142
+ # Get the hash
143
+ facet_hash = @slingshot_search.instance_variable_get(:@facets)
144
+ # Create a new Facet
145
+ facet_obj = Slingshot::Search::Facet.new(name, options)
146
+ facet_obj.terms(field)
147
+ # Initialize the hash if its nil
148
+ if facet_hash.nil?
149
+ @slingshot_search.instance_variable_set(:@facets, {})
150
+ end
151
+ # Add the facet to the hash
152
+ @slingshot_search.instance_variable_get(:@facets).update facet_obj.to_hash
153
+ self
154
+ end
155
+
156
+ # Filters the results according to the criteria
157
+ # @param [*Hash] fields hash for each filter
158
+ # @return [Mebla::Search]
159
+ #
160
+ # Get all indexed Posts and filter them by tags and authors::
161
+ #
162
+ # Post.search("*").only(:tag => ["ruby", "rails"], :author => ["cousine"])
163
+ def only(*fields)
164
+ return if fields.empty?
165
+ fields.each do |field|
166
+ @slingshot_search = @slingshot_search.filter(:terms, field)
167
+ end
168
+ self
169
+ end
170
+
171
+ # Sets the maximum number of hits per query, defaults to 10
172
+ # @param [Integer] value
173
+ # @return [Mebla::Search]
174
+ def size(value)
175
+ @slingshot_search = @slingshot_search.size(value)
176
+ self
177
+ end
178
+
179
+ # Sets the starting offset for the query
180
+ # @param [Integer] value
181
+ # @return [Mebla::Search]
182
+ def from(value)
183
+ @slingshot_search = @slingshot_search.from(value)
184
+ self
185
+ end
186
+
187
+ # Performs the search and returns the results
188
+ # @return [Mebla::ResultSet]
189
+ def hits
190
+ return @results if @results
191
+ # Log search query
192
+ Mebla.log("Searching:\n#{@slingshot_search.to_json.to_s}", :debug)
193
+ response = @slingshot_search.perform.json
194
+ Mebla.log("Response:\n#{response.to_json.to_s}", :info)
195
+ @results = Mebla::ResultSet.new(response)
196
+ # Log results statistics
197
+ Mebla.log("Searched for:\n#{@slingshot_search.to_json.to_s}\ngot #{@results.total} documents in #{@results.time} ms", :debug)
198
+ @results
199
+ end
200
+
201
+ # Returns the internal results list
202
+ # @return [Array]
203
+ def entries
204
+ hits.entries
205
+ end
206
+
207
+ # Retrieves the total number of hits
208
+ # @return [Integer]
209
+ def total
210
+ hits.total
211
+ end
212
+
213
+ # Retrieves the time taken to perform the search in ms
214
+ # @return [Float]
215
+ def time
216
+ hits.time
217
+ end
218
+
219
+ # Retrieves the facets
220
+ # @return [Hash]
221
+ #
222
+ # Reading a facet named 'tags'::
223
+ #
224
+ # facets = Post.search("*").facet("tags", :tag)
225
+ # facets["terms"].each do |term|
226
+ # puts "#{term['term']} - #{term['count']}"
227
+ # end
228
+ def facets
229
+ hits.facets
230
+ end
231
+
232
+ # Iterates over the results collection
233
+ def each(&block)
234
+ hits.each(&block)
235
+ end
236
+
237
+ alias_method :asc, :ascending
238
+ alias_method :desc, :descending
239
+ end
240
+ end
@@ -0,0 +1,49 @@
1
+ namespace :mebla do
2
+ desc "Creates the indeces and indexes the data for all indexed models"
3
+ task :index => :environment do
4
+ setup
5
+ @context.index_data
6
+ end
7
+
8
+ desc "Drops then creates the indeces and indexes the data for all indexed models"
9
+ task :reindex => :environment do
10
+ setup
11
+ @context.reindex_data
12
+ end
13
+
14
+ desc "Creates the index without indexing the data"
15
+ task :create_index => :environment do
16
+ setup
17
+ @context.create_index
18
+ end
19
+
20
+ desc "Rebuilds the index without indexing the data"
21
+ task :rebuild_index => :environment do
22
+ setup
23
+ @context.rebuild_index
24
+ end
25
+
26
+ desc "Drops the index"
27
+ task :drop_index => :environment do
28
+ setup
29
+ @context.drop_index
30
+ end
31
+
32
+ desc "Refreshes the index"
33
+ task :refresh_index => :environment do
34
+ setup
35
+ @context.refresh_index
36
+ end
37
+ end
38
+
39
+ # @private
40
+ # Sets up the logger and loads all the models
41
+ def setup
42
+ Rails.application.eager_load!
43
+ Mebla.configure do |config|
44
+ config.logger = ActiveSupport::BufferedLogger.new(STDOUT)
45
+ config.logger.level = ActiveSupport::BufferedLogger::Severity::UNKNOWN
46
+ config.setup_logger
47
+ end
48
+ @context = Mebla.context
49
+ end
@@ -0,0 +1,113 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "rmla"
8
+ s.version = "1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("<= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Youssef Hanna"]
12
+ s.date = "2012-01-04"
13
+ s.description = "\n An elasticsearch wrapper for mongoid odm based on slingshot. Makes integration between ElasticSearch full-text \n search engine and Mongoid documents seemless and simple.\n "
14
+ s.email = "1@11.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "TODO.md",
28
+ "VERSION",
29
+ "lib/generators/mebla/install/USAGE",
30
+ "lib/generators/mebla/install/install_generator.rb",
31
+ "lib/generators/mebla/install/templates/mebla.yml",
32
+ "lib/mebla.rb",
33
+ "lib/mebla/configuration.rb",
34
+ "lib/mebla/context.rb",
35
+ "lib/mebla/errors.rb",
36
+ "lib/mebla/errors/mebla_configuration_exception.rb",
37
+ "lib/mebla/errors/mebla_error.rb",
38
+ "lib/mebla/errors/mebla_fatal.rb",
39
+ "lib/mebla/errors/mebla_index_exception.rb",
40
+ "lib/mebla/errors/mebla_synchronization_exception.rb",
41
+ "lib/mebla/log_subscriber.rb",
42
+ "lib/mebla/mongoid/mebla.rb",
43
+ "lib/mebla/railtie.rb",
44
+ "lib/mebla/result_set.rb",
45
+ "lib/mebla/search.rb",
46
+ "lib/mebla/tasks.rb",
47
+ "mebla.gemspec",
48
+ "spec/fixtures/models.rb",
49
+ "spec/fixtures/mongoid.yml",
50
+ "spec/mebla/indexing_spec.rb",
51
+ "spec/mebla/searching_spec.rb",
52
+ "spec/mebla/synchronizing_spec.rb",
53
+ "spec/mebla_helper.rb",
54
+ "spec/mebla_spec.rb",
55
+ "spec/spec_helper.rb",
56
+ "spec/support/mongoid.rb",
57
+ "spec/support/rails.rb"
58
+ ]
59
+ s.homepage = "http://github.com/cousine/mebla"
60
+ s.licenses = ["MIT"]
61
+ s.require_paths = ["lib"]
62
+ s.rubygems_version = "1.8.11"
63
+ s.summary = "An elasticsearch wrapper for mongoid odm based on slingshot."
64
+
65
+ if s.respond_to? :specification_version then
66
+ s.specification_version = 3
67
+
68
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
69
+ s.add_runtime_dependency(%q<slingshot-rb>, ["= 0.0.8"])
70
+ s.add_runtime_dependency(%q<mongoid>, [">= 2.1.8"])
71
+ s.add_development_dependency(%q<bson_ext>, ["~> 1.3.1"])
72
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
73
+ s.add_development_dependency(%q<rcov>, [">= 0"])
74
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
75
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
76
+ s.add_development_dependency(%q<mongoid-rspec>, ["~> 1.4.4"])
77
+ s.add_development_dependency(%q<database_cleaner>, ["= 0.6.4"])
78
+ s.add_development_dependency(%q<bluecloth>, ["~> 2.1.0"])
79
+ s.add_development_dependency(%q<irbtools>, [">= 0"])
80
+ s.add_development_dependency(%q<ruby-debug>, [">= 0"])
81
+ s.add_development_dependency(%q<ruby-debug19>, [">= 0"])
82
+ else
83
+ s.add_dependency(%q<slingshot-rb>, ["= 0.0.8"])
84
+ s.add_dependency(%q<mongoid>, [">= 2.1.8"])
85
+ s.add_dependency(%q<bson_ext>, ["~> 1.3.1"])
86
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
87
+ s.add_dependency(%q<rcov>, [">= 0"])
88
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
89
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
90
+ s.add_dependency(%q<mongoid-rspec>, ["~> 1.4.4"])
91
+ s.add_dependency(%q<database_cleaner>, ["= 0.6.4"])
92
+ s.add_dependency(%q<bluecloth>, ["~> 2.1.0"])
93
+ s.add_dependency(%q<irbtools>, [">= 0"])
94
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
95
+ s.add_dependency(%q<ruby-debug19>, [">= 0"])
96
+ end
97
+ else
98
+ s.add_dependency(%q<slingshot-rb>, ["= 0.0.8"])
99
+ s.add_dependency(%q<mongoid>, [">= 2.1.8"])
100
+ s.add_dependency(%q<bson_ext>, ["~> 1.3.1"])
101
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
102
+ s.add_dependency(%q<rcov>, [">= 0"])
103
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
104
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
105
+ s.add_dependency(%q<mongoid-rspec>, ["~> 1.4.4"])
106
+ s.add_dependency(%q<database_cleaner>, ["= 0.6.4"])
107
+ s.add_dependency(%q<bluecloth>, ["~> 2.1.0"])
108
+ s.add_dependency(%q<irbtools>, [">= 0"])
109
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
110
+ s.add_dependency(%q<ruby-debug19>, [">= 0"])
111
+ end
112
+ end
113
+