bigindex 0.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.
Files changed (54) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +58 -0
  3. data/Rakefile +14 -0
  4. data/VERSION +1 -0
  5. data/examples/bigindex.yml +9 -0
  6. data/generators/bigindex/bigindex_generator.rb +17 -0
  7. data/generators/bigindex/templates/bigindex.rake +3 -0
  8. data/init.rb +27 -0
  9. data/install.rb +15 -0
  10. data/lib/big_index/adapters/abstract_adapter.rb +70 -0
  11. data/lib/big_index/adapters/solr_adapter.rb +180 -0
  12. data/lib/big_index/adapters.rb +11 -0
  13. data/lib/big_index/index_field.rb +41 -0
  14. data/lib/big_index/repository.rb +77 -0
  15. data/lib/big_index/resource.rb +462 -0
  16. data/lib/big_index/support/assertions.rb +8 -0
  17. data/lib/big_index/support.rb +3 -0
  18. data/lib/big_index.rb +108 -0
  19. data/lib/bigindex.rb +1 -0
  20. data/rails/init.rb +27 -0
  21. data/spec/connections/activerecord/activerecord.yml +7 -0
  22. data/spec/connections/activerecord/connection.rb +19 -0
  23. data/spec/connections/bigindex.yml +7 -0
  24. data/spec/connections/bigrecord/bigrecord.yml +13 -0
  25. data/spec/connections/bigrecord/connection.rb +29 -0
  26. data/spec/connections/bigrecord/migrations/20090706182535_add_animals_table.rb +13 -0
  27. data/spec/connections/bigrecord/migrations/20090706190623_add_books_table.rb +15 -0
  28. data/spec/connections/bigrecord/migrations/20090706193019_add_companies_table.rb +14 -0
  29. data/spec/connections/bigrecord/migrations/20090706194512_add_employees_table.rb +13 -0
  30. data/spec/connections/bigrecord/migrations/20090706195741_add_zoos_table.rb +13 -0
  31. data/spec/lib/activerecord/animal.rb +14 -0
  32. data/spec/lib/activerecord/book.rb +26 -0
  33. data/spec/lib/activerecord/novel.rb +10 -0
  34. data/spec/lib/bigrecord/animal.rb +11 -0
  35. data/spec/lib/bigrecord/book.rb +27 -0
  36. data/spec/lib/bigrecord/novel.rb +7 -0
  37. data/spec/spec.opts +4 -0
  38. data/spec/spec_helper.rb +28 -0
  39. data/spec/unit/adapters/abstract_adapter_spec.rb +48 -0
  40. data/spec/unit/adapters/adapter_shared_spec.rb +10 -0
  41. data/spec/unit/adapters/solr_adapter_spec.rb +16 -0
  42. data/spec/unit/bigindex_setup_spec.rb +70 -0
  43. data/spec/unit/index_shared_spec.rb +59 -0
  44. data/spec/unit/index_spec.rb +225 -0
  45. data/spec/unit/inherited_class_spec.rb +42 -0
  46. data/tasks/gem.rb +20 -0
  47. data/tasks/rdoc.rb +8 -0
  48. data/tasks/spec.rb +38 -0
  49. data/vendor/solr/adapter_methods/search_results.rb +53 -0
  50. data/vendor/solr/adapter_methods/solr_result.rb +137 -0
  51. data/vendor/solr/adapter_methods.rb +360 -0
  52. data/vendor/solr/base.rb +159 -0
  53. data/vendor/solr.rb +20 -0
  54. metadata +147 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 openplaces.org
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,58 @@
1
+ = BigIndex
2
+
3
+ A Rails plugin that drops into models and provides indexing functionality. Uses an adapter/repository pattern inspired by Datamapper to abstract the actual indexer used in the background, and exposes the model to a simple indexing API.
4
+
5
+ This should be used in conjunction with BigRecord in order to provide a more complete ORM.
6
+
7
+ == Supported search servers
8
+
9
+ * Solr
10
+ * Sphinx (planned)
11
+
12
+ == Installation
13
+
14
+ (1) Download and install Solr. Take a note of the url that solr is running on.
15
+
16
+ (2) In your Rails application, add Bigindex as a gem to your config/environment.rb file:
17
+
18
+ config.gem "bigindex", :source => "http://gemcutter.org"
19
+
20
+ and run the following rake task to install all the gems listed for your Rails app:
21
+
22
+ [sudo] rake gems:install
23
+
24
+ (3) Bootstrap Bigindex into your Rails application:
25
+
26
+ script/generate bigindex
27
+
28
+ (4) Modify the config file config/bigindex.yml[.sample] to correspond to your Solr server.
29
+
30
+ == Getting Started
31
+
32
+ Modify your Ruby class/model similarly to the following:
33
+
34
+ class Model < BigRecord::Base
35
+ include BigIndex::Resource # 1. Include the BigIndex::Resource module into your model.
36
+
37
+ column :name, :string
38
+ column :description, :text
39
+
40
+ index :name, :string # 2. Define each attribute you want to index along with its type.
41
+ index :description # Defaults to type :text
42
+ end
43
+
44
+ BigIndex will then override the default Model.find() method to pass through the indexer first. Model.find() will also accept the option {:bypass_index => true}, which bypasses the indexed #find method and dispatches it to the original Model.find() method, e.g. Model.find(:all, :bypass_index => true). Alternatively, you can use Model.find_without_index(:all) for the same functionality.
45
+
46
+ == License
47
+
48
+ Bigindex is released under the MIT license.
49
+
50
+ == Contributions
51
+
52
+ Bigindex was derived from the work of Data Mapper and parts of acts_as_solr.
53
+
54
+ == Links
55
+
56
+ * Contact Us
57
+ * Website - http://www.bigrecord.org
58
+ * IRC Channel - <tt>#bigrecord</tt> on irc.freenode.net
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'pathname'
6
+ require 'spec/rake/spectask'
7
+
8
+ DATA_STORES = ["bigrecord", "activerecord"]
9
+
10
+ ROOT = Pathname(__FILE__).dirname.expand_path
11
+
12
+ require ROOT + 'tasks/gem'
13
+ require ROOT + 'tasks/rdoc'
14
+ require ROOT + 'tasks/spec'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,9 @@
1
+ development:
2
+ adapter: solr
3
+ solr_url: http://localhost:8981/solr
4
+ test:
5
+ adapter: solr
6
+ solr_url: http://localhost:8982/solr
7
+ production:
8
+ adapter: solr
9
+ solr_url: http://localhost:8983/solr
@@ -0,0 +1,17 @@
1
+ # This generator bootstraps a Rails project for use with Bigindex
2
+ class BigindexGenerator < Rails::Generator::Base
3
+
4
+ def initialize(runtime_args, runtime_options = {})
5
+ require File.join(File.dirname(__FILE__), "..", "..", "install.rb")
6
+ Dir.mkdir('lib/tasks') unless File.directory?('lib/tasks')
7
+ super
8
+ end
9
+
10
+ def manifest
11
+ record do |m|
12
+ m.directory 'lib/tasks'
13
+ m.file 'bigindex.rake', 'lib/tasks/bigindex.rake'
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,3 @@
1
+ namespace :bigrecord do
2
+
3
+ end
data/init.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'big_index'
2
+
3
+ def config_file
4
+ "#{RAILS_ROOT}/config/bigindex.yml"
5
+ end
6
+
7
+ def full_config
8
+ begin
9
+ YAML::load(File.open(config_file))
10
+ rescue
11
+ puts "Missing environment '#{RAILS_ENV}' in config file #{config_file}"
12
+ return {}
13
+ end
14
+ end
15
+
16
+ def get_config_for_environment
17
+ if hash = full_config[RAILS_ENV]
18
+ BigIndex.symbolize_keys(hash)
19
+ elsif hash = full_config[RAILS_ENV.to_sym]
20
+ hash
21
+ else
22
+ {}
23
+ end
24
+ end
25
+
26
+
27
+ BigIndex.setup(:default, get_config_for_environment) unless get_config_for_environment.empty?
data/install.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+
3
+ puts "[Bigindex] Copying example config file to your RAILS_ROOT...\n"
4
+
5
+ config_dir = File.join(RAILS_ROOT, "config")
6
+ source = File.join(File.dirname(__FILE__), "examples", "bigindex.yml")
7
+ target = File.join(config_dir, "bigindex.yml")
8
+ alternate_target = File.join(config_dir, "bigindex.yml.sample")
9
+
10
+ if !File.exist?(target)
11
+ FileUtils.cp(source, target)
12
+ else
13
+ puts "[Bigindex] RAILS_ROOT/config/bigindex.yml file already exists. Copying it as bigindex.yml.sample for reference."
14
+ FileUtils.cp(source, alternate_target)
15
+ end
@@ -0,0 +1,70 @@
1
+ module BigIndex
2
+ module Adapters
3
+
4
+ class AbstractAdapter
5
+
6
+ attr_reader :name, :options, :connection
7
+
8
+ def adapter_name
9
+ 'abstract'
10
+ end
11
+
12
+ def default_type_field
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def default_primary_key_field
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def process_index_batch(items, loop, options={})
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def drop_index(model)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def get_field_type(field_type)
29
+ field_type
30
+ end
31
+
32
+ def execute(request)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ def index_save(model)
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def index_destroy(model)
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def find_by_index(model, query, options={})
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def find_values_by_index(model, query, options={})
49
+ raise NotImplementedError
50
+ end
51
+
52
+ def find_ids_by_index(model, query, options={})
53
+ raise NotImplementedError
54
+ end
55
+
56
+ def optimize_index
57
+ raise NotImplementedError
58
+ end
59
+
60
+ private
61
+
62
+ def initialize(name, options)
63
+ @name = name
64
+ @options = options
65
+ end
66
+
67
+ end # class AbstractAdapter
68
+
69
+ end # module Adapters
70
+ end # module BigIndex
@@ -0,0 +1,180 @@
1
+ module BigIndex
2
+ module Adapters
3
+
4
+ class SolrAdapter < AbstractAdapter
5
+
6
+ include ::Solr::AdapterMethods
7
+
8
+ # BigIndex Adapter API methods ====================================
9
+
10
+ def adapter_name
11
+ 'solr'
12
+ end
13
+
14
+ def default_type_field
15
+ "type_s_mv"
16
+ end
17
+
18
+ def default_primary_key_field
19
+ "pk_s"
20
+ end
21
+
22
+ def process_index_batch(items, loop, options = {})
23
+ unless items.empty?
24
+ # This checks that if the item has a method indexable? defined, then it will determine
25
+ # whether or not to index the item based on that method's returned boolean value.
26
+ items_to_index = items.select { |item| item.respond_to?(:indexable?) ? item.indexable? : true }
27
+
28
+ unless items_to_index.empty?
29
+ docs = items_to_index.collect{|content| to_solr_doc(content)}
30
+ if options[:only_generate]
31
+ # Collect the documents. This is to be used within a mapred job.
32
+ docs.each do |doc|
33
+ key = doc['id']
34
+
35
+ # Cannot have \n and \t in the value since they are
36
+ # document and field separators respectively
37
+ value = doc.to_xml.to_s
38
+ value = value.gsub("\n", "__ENDLINE__")
39
+ value = value.gsub("\t", "__TAB__")
40
+
41
+ puts "#{key}\t#{value}"
42
+ end
43
+ else
44
+ solr_add(docs)
45
+ solr_commit if options[:commit]
46
+ end
47
+ else
48
+ break
49
+ end
50
+ end
51
+ end
52
+
53
+ def drop_index(model)
54
+ @connection.logger = model.logger if model.respond_to?(:logger)
55
+ result = @connection.solr_execute(Solr::Request::Delete.new(:query => "type_s_mv:\"#{model.name}\""))
56
+
57
+ result.status_message == "0"
58
+ end
59
+
60
+ def get_field_type(field_type)
61
+ if field_type.is_a?(Symbol)
62
+ case field_type
63
+ when :float then return "f"
64
+ when :integer then return "i"
65
+ when :boolean then return "b"
66
+ when :string then return "s"
67
+ when :date then return "d"
68
+ when :range_float then return "rf"
69
+ when :range_integer then return "ri"
70
+ when :ngrams then return "ngrams"
71
+ when :autocomplete then return "auto"
72
+ when :lowercase then return "lc"
73
+ when :exact_match then return "em"
74
+ when :geo then return "geo"
75
+ when :text then return "t"
76
+ when :text_not_stored then return "t_ns"
77
+ when :text_not_indexed then return "t_ni"
78
+ when :integer_array then return "i_mv"
79
+ when :integer_array_not_stored then return "i_mv_ns"
80
+ when :text_array then return "t_mv"
81
+ when :text_array_not_stored then return "t_mv_ns"
82
+ when :float_array then return "f_mv"
83
+ when :boolean_array then return "b_mv"
84
+ when :date_array then return "d_mv"
85
+ when :string_array then return "s_mv"
86
+ when :range_integer_array then return "ri_mv"
87
+ when :range_float_array then return "rf_mv"
88
+ when :ngrams_array then return "ngrams_mv"
89
+ when :autocomplete_array then return "auto_mv"
90
+ when :lowercase_array then return "lc_mv"
91
+ when :exact_match_array then return "em_mv"
92
+ else
93
+ raise "Unknown field_type symbol: #{field_type}"
94
+ end
95
+ elsif field_type.is_a?(String)
96
+ return field_type
97
+ else
98
+ raise "Unknown field_type class: #{field_type.class}: #{field_type}"
99
+ end
100
+ end
101
+
102
+ def execute(request)
103
+ @connection.solr_execute(request)
104
+ end
105
+
106
+ def index_save(model)
107
+ configuration = model.index_configuration
108
+
109
+ results = []
110
+ if configuration[:if] && evaluate_condition(configuration[:if], model)
111
+ results << solr_add(to_solr_doc(model))
112
+ results << solr_commit if configuration[:auto_commit]
113
+ end
114
+
115
+ !results.map{|result| result.status_code == "0"}.include?(false)
116
+ end
117
+
118
+ def index_destroy(model)
119
+ configuration = model.index_configuration
120
+
121
+ results = []
122
+
123
+ results << solr_delete(model.index_id)
124
+ results << solr_delete(":#{model.record_id}")
125
+ results << solr_commit if configuration[:auto_commit]
126
+
127
+ !results.map{|result| result.status_code == "0"}.include?(false)
128
+ end
129
+
130
+ def find_by_index(model, query, options={})
131
+ raw_result = options.delete(:raw_result)
132
+ data = parse_query(model, query, options)
133
+
134
+ if data
135
+ parsed = parse_results(model, data, options)
136
+
137
+ return raw_result ? parsed : parsed.results
138
+ end
139
+ end
140
+
141
+ def find_values_by_index(model, query, options={})
142
+ raw_result = options.delete(:raw_result)
143
+ data = parse_query(model, query, options)
144
+
145
+ if data
146
+ parsed = parse_results(model, data, {:format => :values})
147
+
148
+ return raw_result ? parsed : parsed.results
149
+ end
150
+ end
151
+
152
+ def find_ids_by_index(model, query, options={})
153
+ raw_result = options.delete(:raw_result)
154
+ data = parse_query(model, query, options)
155
+
156
+ if data
157
+ parsed = parse_results(model, data, {:format => :ids})
158
+
159
+ return raw_result ? parsed : parsed.results
160
+ end
161
+ end
162
+
163
+ def optimize_index
164
+ solr_optimize
165
+ end
166
+
167
+ # End of BigIndex Adapter API ====================================
168
+
169
+ private
170
+
171
+ def initialize(name, options)
172
+ @connection = Solr::Base.new(options)
173
+
174
+ super(name, options)
175
+ end
176
+
177
+ end # class SolrAdapter
178
+
179
+ end # module Adapters
180
+ end # module BigIndex
@@ -0,0 +1,11 @@
1
+ dir = Pathname(__FILE__).dirname.expand_path + 'adapters'
2
+
3
+ require dir + 'abstract_adapter'
4
+
5
+ %w[ solr ].each do |gem|
6
+ begin
7
+ require dir + "#{gem}_adapter"
8
+ rescue LoadError, Gem::Exception
9
+ # ignore it
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), 'support')
2
+
3
+ module BigIndex
4
+
5
+ class IndexField
6
+ include Assertions
7
+
8
+ attr_reader :field, :field_name, :field_type, :options, :block
9
+
10
+ def initialize(params, block = nil)
11
+ raise "IndexField requires at least a field name" unless params.size > 0
12
+
13
+ @params = params.dup
14
+ @block = block
15
+
16
+ @field_name = params.shift
17
+ assert_kind_of 'field_name', @field_name, Symbol, String
18
+
19
+ unless params.empty? || ![Symbol, String].include?(params.first.class)
20
+ @field_type = params.shift
21
+ end
22
+
23
+ @options = params.shift || {}
24
+ assert_kind_of 'options', @options, Hash
25
+
26
+ # Setting the default values
27
+ @options[:finder_name] ||= field_name
28
+ @field_type ||= :text
29
+ end
30
+
31
+ def [](name)
32
+ @options[name]
33
+ end
34
+
35
+ def method_missing(name)
36
+ @options[name.to_sym] || super
37
+ end
38
+
39
+ end # class IndexField
40
+
41
+ end # module BigIndex
@@ -0,0 +1,77 @@
1
+ module BigIndex
2
+ class Repository
3
+ include Assertions
4
+
5
+ @adapters = {}
6
+ @default_name = :default
7
+
8
+ ##
9
+ #
10
+ # @return <Adapter> the adapters registered for this repository
11
+ def self.adapters
12
+ @adapters
13
+ end
14
+
15
+ def self.context
16
+ Thread.current[:bigindex_repository_contexts] ||= []
17
+ end
18
+
19
+ def self.default_name
20
+ @default_name ||= :default
21
+ end
22
+
23
+ def self.default_name=(name)
24
+ @default_name = name
25
+ end
26
+
27
+ # TODO: Make sure this isn't dangerous
28
+ def self.clear_adapters
29
+ @adapters = {}
30
+ end
31
+
32
+ attr_reader :name
33
+
34
+ def adapter
35
+ # Make adapter instantiation lazy so we can defer repository setup until it's actually
36
+ # needed. Do not remove this code.
37
+ @adapter ||= begin
38
+ raise ArgumentError, "Adapter not set: #{@name}. Did you forget to setup?" \
39
+ unless self.class.adapters.has_key?(@name)
40
+
41
+ self.class.adapters[@name]
42
+ end
43
+ end
44
+
45
+ # TODO: spec this
46
+ def scope
47
+ Repository.context << self
48
+
49
+ begin
50
+ return yield(self)
51
+ ensure
52
+ Repository.context.pop
53
+ end
54
+ end
55
+
56
+ def eql?(other)
57
+ return true if super
58
+ name == other.name
59
+ end
60
+
61
+ alias == eql?
62
+
63
+ def to_s
64
+ "#<BigIndex::Repository:#{@name}>"
65
+ end
66
+
67
+
68
+ private
69
+
70
+ def initialize(name)
71
+ assert_kind_of 'name', name, Symbol
72
+
73
+ @name = name
74
+ end
75
+
76
+ end # class Repository
77
+ end # module BigIndex