bigindex 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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