rmla 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+