dnif 0.0.1.alpha.2

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,57 @@
1
+ = dnif
2
+
3
+ dnif is the new find... for sphinx
4
+
5
+ dnif is a gem to index data using ActiveRecord finders, letting you index your custom methods and not your table fields
6
+
7
+ == Usage
8
+
9
+ require "dnif"
10
+
11
+ class Post < ActiveRecord::Base
12
+
13
+ define_index do
14
+ field :title
15
+ field :body
16
+ attribute :published_at, :type => :datetime
17
+
18
+ where ["draft = ?", false]
19
+ end
20
+
21
+ def slug
22
+ self.title.parameterize
23
+ end
24
+ end
25
+
26
+ Post.search("dnif")
27
+
28
+ == TODO
29
+
30
+ - Improve the test suite
31
+
32
+ == Maintainer
33
+
34
+ * Rafael Souza - http://rafaelss.com/
35
+
36
+ == License
37
+
38
+ (The MIT License)
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining
41
+ a copy of this software and associated documentation files (the
42
+ 'Software'), to deal in the Software without restriction, including
43
+ without limitation the rights to use, copy, modify, merge, publish,
44
+ distribute, sublicense, and/or sell copies of the Software, and to
45
+ permit persons to whom the Software is furnished to do so, subject to
46
+ the following conditions:
47
+
48
+ The above copyright notice and this permission notice shall be
49
+ included in all copies or substantial portions of the Software.
50
+
51
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
52
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
53
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
54
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
55
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
56
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
57
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ begin
2
+ require 'jeweler'
3
+
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = "dnif"
6
+ gemspec.summary = "dnif is the new find... for sphinx"
7
+ gemspec.description = "dnif is a gem to index data using ActiveRecord finders, letting you index your custom methods and not your table fields "
8
+ gemspec.email = "me@rafaelss.com"
9
+ gemspec.homepage = "http://github.com/rafaelss/dnif"
10
+ gemspec.authors = ["Rafael Souza"]
11
+
12
+ gemspec.has_rdoc = false
13
+ gemspec.files = %w(Rakefile dnif.gemspec README.rdoc) + Dir["{lib,test}/**/*"]
14
+
15
+ gemspec.add_dependency "activerecord"
16
+ gemspec.add_dependency "activesupport"
17
+ gemspec.add_dependency "riddle"
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ desc "Generate gemspec and build gem"
24
+ task :build_gem do
25
+ Rake::Task["gemspec"].invoke
26
+ Rake::Task["build"].invoke
27
+ end
28
+
29
+ Jeweler::GemcutterTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.test_files = FileList.new('test/**/test_*.rb') do |list|
34
+ list.exclude 'test/test_helper.rb'
35
+ end
36
+ test.libs << 'test'
37
+ test.verbose = true
38
+ end
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dnif}
8
+ s.version = "0.0.1.alpha.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Rafael Souza"]
12
+ s.date = %q{2010-07-01}
13
+ s.description = %q{dnif is a gem to index data using ActiveRecord finders, letting you index your custom methods and not your table fields }
14
+ s.email = %q{me@rafaelss.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "dnif.gemspec",
22
+ "lib/dnif.rb",
23
+ "lib/dnif/configuration.rb",
24
+ "lib/dnif/index_builder.rb",
25
+ "lib/dnif/indexer.rb",
26
+ "lib/dnif/multi_attribute.rb",
27
+ "lib/dnif/search.rb",
28
+ "lib/dnif/tasks.rb",
29
+ "test/fixtures/db/schema.rb",
30
+ "test/fixtures/log/searchd.pid",
31
+ "test/fixtures/models.rb",
32
+ "test/fixtures/templates/config.erb",
33
+ "test/test_helper.rb",
34
+ "test/unit/test_configuration.rb",
35
+ "test/unit/test_dnif.rb",
36
+ "test/unit/test_index_builder.rb",
37
+ "test/unit/test_indexer.rb",
38
+ "test/unit/test_multi_attribute.rb",
39
+ "test/unit/test_search.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/rafaelss/dnif}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.7}
45
+ s.summary = %q{dnif is the new find... for sphinx}
46
+ s.test_files = [
47
+ "test/fixtures/db/schema.rb",
48
+ "test/fixtures/models.rb",
49
+ "test/test_helper.rb",
50
+ "test/unit/test_configuration.rb",
51
+ "test/unit/test_dnif.rb",
52
+ "test/unit/test_index_builder.rb",
53
+ "test/unit/test_indexer.rb",
54
+ "test/unit/test_multi_attribute.rb",
55
+ "test/unit/test_search.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
64
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
65
+ s.add_runtime_dependency(%q<riddle>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<activerecord>, [">= 0"])
68
+ s.add_dependency(%q<activesupport>, [">= 0"])
69
+ s.add_dependency(%q<riddle>, [">= 0"])
70
+ end
71
+ else
72
+ s.add_dependency(%q<activerecord>, [">= 0"])
73
+ s.add_dependency(%q<activesupport>, [">= 0"])
74
+ s.add_dependency(%q<riddle>, [">= 0"])
75
+ end
76
+ end
77
+
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_record'
4
+ require 'active_support'
5
+ require 'riddle'
6
+
7
+ require "dnif/configuration"
8
+ require "dnif/index_builder"
9
+ require "dnif/indexer"
10
+ require "dnif/multi_attribute"
11
+ require "dnif/search"
12
+
13
+ module Dnif
14
+
15
+ def self.root_path
16
+ @root_path
17
+ end
18
+
19
+ def self.root_path=(value)
20
+ @root_path = value
21
+ end
22
+
23
+ def self.environment
24
+ @environment || 'development'
25
+ end
26
+
27
+ def self.environment=(value)
28
+ @environment = value
29
+ end
30
+
31
+ def self.models_path
32
+ @models_path
33
+ end
34
+
35
+ def self.models_path=(value)
36
+ @models_path = value
37
+ end
38
+
39
+ def self.load_models
40
+ models = Dir["#{self.models_path}/*.rb"]
41
+ models.map! do |filename|
42
+ filename = File.basename(filename, '.rb')
43
+ filename.classify.constantize
44
+ end
45
+ end
46
+ end
47
+
48
+ if defined?(Rails)
49
+ Dnif.root_path = RAILS_ROOT
50
+ Dnif.environment = RAILS_ENV
51
+ Dnif.models_path = File.join(RAILS_ROOT, "app", "models")
52
+ end
@@ -0,0 +1,30 @@
1
+ require 'fileutils'
2
+ require 'tilt'
3
+ require 'dnif'
4
+
5
+ module Dnif
6
+
7
+ class Configuration
8
+
9
+ def self.generate(config_path)
10
+ Tilt.register 'erb', Tilt::ErubisTemplate
11
+
12
+ template = Tilt.new(config_path)
13
+ output = template.render(self)
14
+
15
+ # TODO turn "db/sphinx" and "config/sphinx" configurable
16
+ FileUtils.mkdir_p(File.join(Dnif.root_path, "db", "sphinx", Dnif.environment))
17
+ File.open(Dnif.root_path + "/config/sphinx/" + Dnif.environment + ".conf", "w") do |f|
18
+ f.puts output
19
+ end
20
+ end
21
+
22
+ def self.sources
23
+ classes = ActiveRecord::Base.classes.keys
24
+ classes.each do |class_name|
25
+ name = class_name.underscore.pluralize + "_main"
26
+ yield name, class_name
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Dnif
2
+ class IndexBuilder
3
+
4
+ attr_reader :fields
5
+ attr_reader :attributes
6
+ attr_reader :conditions
7
+
8
+ def initialize(object, &block)
9
+ @fields = []
10
+ @attributes = {}
11
+
12
+ @object = object
13
+ self.instance_eval(&block)
14
+ end
15
+
16
+ def field(name)
17
+ @fields << name
18
+ end
19
+
20
+ def attribute(name, options)
21
+ raise "You must specify the attribute type (:integer, :datetime, :date, :boolean, :float)" if options[:type].nil?
22
+
23
+ @attributes[name] = options[:type]
24
+ end
25
+
26
+ def where(conditions)
27
+ @conditions = conditions
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ module Dnif
2
+ module Indexer
3
+
4
+ def define_index(&block)
5
+ classes[self.name] = IndexBuilder.new(self, &block)
6
+ classes[self.name]
7
+
8
+ include InstanceMethods
9
+ end
10
+
11
+ def classes
12
+ @@classes ||= ActiveSupport::OrderedHash.new
13
+ end
14
+
15
+ def to_sphinx
16
+ return nil if classes.blank?
17
+
18
+ returning('') do |xml|
19
+ builder = classes[self.name]
20
+ results = all(:conditions => builder.conditions)
21
+
22
+ xml << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sphinx:docset>\n"
23
+
24
+ xml << "<sphinx:schema>\n"
25
+ builder.fields.each do |name|
26
+ xml << " <sphinx:field name=\"#{name}\"/>\n"
27
+ end
28
+
29
+ xml << " <sphinx:attr name=\"class_id\" type=\"multi\"/>\n"
30
+ builder.attributes.each do |name, type|
31
+ xml << " <sphinx:attr name=\"#{name}\" "
32
+
33
+ case type
34
+ when :integer
35
+ xml << "type=\"int\""
36
+ when :date, :datetime
37
+ xml << "type=\"timestamp\""
38
+ when :boolean
39
+ xml << "type=\"bool\""
40
+ when :float
41
+ xml << "type=\"float\""
42
+ end
43
+
44
+ xml << "/>\n"
45
+ end
46
+
47
+ xml << "</sphinx:schema>\n"
48
+
49
+ results.each do |object|
50
+ xml << object.to_sphinx
51
+ end
52
+ xml << "</sphinx:docset>"
53
+ end
54
+ end
55
+
56
+ module InstanceMethods
57
+
58
+ def to_sphinx
59
+ builder = ActiveRecord::Base.classes[self.class.name]
60
+ if not builder.nil?
61
+ class_id = Dnif::MultiAttribute.encode(self.class.name)
62
+ sphinx_id = id + class_id.split(',').sum { |c| c.to_i }
63
+ xml = "<sphinx:document id=\"#{sphinx_id}\">\n"
64
+
65
+ builder.fields.each do |field|
66
+ xml << " <#{field}><![CDATA[[#{send(field)}]]></#{field}>\n"
67
+ end
68
+
69
+ xml << " <class_id>#{class_id}</class_id>\n"
70
+
71
+ builder.attributes.each do |name, type|
72
+ value = send(name)
73
+
74
+ if [:date, :datetime].include?(builder.attributes[name])
75
+ if value.is_a?(Date)
76
+ value = value.to_datetime
77
+ end
78
+
79
+ value = value.to_i
80
+ end
81
+
82
+ xml << " <#{name}>#{value}</#{name}>\n"
83
+ end
84
+
85
+ xml << "</sphinx:document>\n"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ ActiveRecord::Base.extend(Dnif::Indexer)
@@ -0,0 +1,52 @@
1
+ # Code extracted from MongoSphinx
2
+ # http://github.com/burke/mongosphinx/blob/master/lib/multi_attribute.rb
3
+ module Dnif
4
+
5
+ # Module MultiAttribute implements helpers to translate back and
6
+ # forth between Ruby Strings and an array of integers suitable for Sphinx
7
+ # attributes of type "multi".
8
+ #
9
+ # Background: Getting an ID as result for a query is OK, but for example to
10
+ # allow cast safety, we need an aditional attribute. Sphinx supports
11
+ # attributes which are returned together with the ID, but they behave a
12
+ # little different than expected: Instead we can use arrays of integers with
13
+ # ASCII character codes. These values are returned in ascending (!) order of
14
+ # value (yes, sounds funny but is reasonable from an internal view to
15
+ # Sphinx). So we mask each byte with 0x0100++ to keep the order...
16
+ #
17
+ # Sample:
18
+ #
19
+ # MongoSphinx::MultiAttribute.encode('Hello')
20
+ # => "328,613,876,1132,1391"
21
+ # MongoSphinx::MultiAttribute.decode('328,613,876,1132,1391')
22
+ # => "Hello"
23
+
24
+ module MultiAttribute
25
+
26
+ # Returns an numeric representation of a Ruby String suitable for "multi"
27
+ # attributes of Sphinx.
28
+ #
29
+ # Parameters:
30
+ #
31
+ # [str] String to translate
32
+
33
+ def self.encode(str)
34
+ offset = 0
35
+ return str.bytes.collect { |c| (offset+= 0x0100) + c }.join(',')
36
+ end
37
+
38
+ # Returns the original MongoDB ID created from a Sphinx ID. Only works if
39
+ # the ID was created from a MongoDB ID before!
40
+ #
41
+ # Parameters:
42
+ #
43
+ # [multi] Sphinx "multi" attribute to translate back
44
+
45
+ def self.decode(multi)
46
+ multi = multi.split(',') if not multi.is_a?(Array)
47
+
48
+ offset = 0
49
+ return multi.collect { |x| (x.to_i - (offset += 0x0100)).chr }.join("")
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ module Dnif
2
+
3
+ def self.search(query, options = {})
4
+ options.reverse_merge!(:index => '*')
5
+
6
+ client = Riddle::Client.new("127.0.0.1", 3313)
7
+
8
+ if not options[:class].nil?
9
+ filter_value = Dnif::MultiAttribute.encode(options[:class]).split(",").map(&:to_i)
10
+ client.filters << Riddle::Client::Filter.new("class_id", filter_value)
11
+ end
12
+
13
+ results = client.query(query, options[:index])
14
+ raise results[:error] if results[:error]
15
+
16
+ models = results[:matches].inject({}) do |memo, match|
17
+ class_id = match[:attributes]["class_id"].split(',')
18
+ class_name = Dnif::MultiAttribute.decode(class_id)
19
+
20
+ memo[class_name] ||= []
21
+ memo[class_name] << (match[:doc] - class_id.sum { |c| c.to_i })
22
+ memo
23
+ end
24
+
25
+ models.map do |class_name, ids|
26
+ class_name.constantize.find_all_by_id(ids)
27
+ end.flatten
28
+ end
29
+
30
+ module Search
31
+
32
+ def search(query)
33
+ Dnif.search(query, :class => self.name)
34
+ end
35
+ end
36
+ end
37
+
38
+ ActiveRecord::Base.extend(Dnif::Search)
@@ -0,0 +1,76 @@
1
+ require 'active_support'
2
+ require 'fileutils'
3
+ require 'dnif'
4
+
5
+ def controller
6
+ require 'riddle'
7
+
8
+ root_path = Dnif.root_path || "."
9
+
10
+ configuration = Riddle::Configuration.new
11
+ configuration.searchd.pid_file = "#{root_path}/log/searchd.#{Dnif.environment}.pid"
12
+ configuration.searchd.log = "#{root_path}/log/searchd.log"
13
+ configuration.searchd.query_log = "#{root_path}/log/searchd.query.log"
14
+
15
+ Riddle::Controller.new(configuration, "#{root_path}/config/sphinx/#{Dnif.environment}.conf")
16
+ end
17
+
18
+ namespace :dnif do
19
+
20
+ task :environment do
21
+ if task = Rake::Task[:environment]
22
+ task.invoke
23
+ end
24
+ end
25
+
26
+ desc "Generates the configuration file needed for sphinx"
27
+ task :configure => :environment do
28
+ if Dnif.models_path.nil?
29
+ puts "You need to specify where are your models (ex: Dnif.models_path = \"path/for/your/models\")"
30
+ exit
31
+ end
32
+
33
+ Dnif.root_path ||= File.expand_path(File.dirname("."))
34
+ Dnif.environment ||= "development"
35
+
36
+ config_path = File.join(Dnif.root_path, "config/sphinx")
37
+ if not File.exist?(config_path)
38
+ FileUtils.mkdir_p(config_path)
39
+ end
40
+
41
+ base_path = File.join(config_path, Dnif.environment + ".erb")
42
+ if not File.exist?(base_path)
43
+ FileUtils.cp(File.dirname(__FILE__) + "/../../test/fixtures/templates/config.erb", base_path) # TODO change this path. find out how this kind of stuff is handle in others gems
44
+ end
45
+
46
+ Dnif.load_models
47
+ Dnif::Configuration.generate(base_path)
48
+ end
49
+
50
+ desc "Generates the XML used by sphinx to create indexes"
51
+ task :xml => :environment do
52
+ ::ActiveRecord::Base.logger = Logger.new(StringIO.new)
53
+ Dnif.load_models
54
+
55
+ klass = ENV['MODEL'].constantize
56
+ puts klass.to_sphinx
57
+ end
58
+
59
+ desc "Index data for sphinx"
60
+ task :index => :environment do
61
+ controller.index(:verbose => true)
62
+ end
63
+
64
+ desc "Stop sphinx daemon"
65
+ task :stop => :environment do
66
+ controller.stop
67
+ end
68
+
69
+ desc "Start sphinx daemon"
70
+ task :start => :environment do
71
+ controller.start
72
+ end
73
+
74
+ desc "Rebuild sphinx index"
75
+ task :rebuild => [:index, :stop, :start]
76
+ end
@@ -0,0 +1,33 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table "comments", :force => true do |t|
4
+ t.string :author
5
+ end
6
+
7
+ create_table "users", :force => true do |t|
8
+ t.string :name
9
+ end
10
+
11
+ create_table "people", :force => true do |t|
12
+ t.string :first_name
13
+ t.string :last_name
14
+ end
15
+
16
+ create_table "posts", :force => true do |t|
17
+ t.string :title
18
+ t.datetime :published_at
19
+ t.boolean :draft, :default => true
20
+ end
21
+
22
+ create_table "sales", :force => true do |t|
23
+ t.datetime :ordered_at
24
+ end
25
+
26
+ create_table "notes", :force => true do |t|
27
+ t.integer :clicked
28
+ t.datetime :published_at
29
+ t.date :expire_at
30
+ t.boolean :active
31
+ t.float :points
32
+ end
33
+ end
File without changes
@@ -0,0 +1,19 @@
1
+ class Post < ActiveRecord::Base
2
+
3
+ define_index do
4
+ field :title
5
+ attribute :published_at, :type => :datetime
6
+ attribute :draft, :type => :boolean
7
+ end
8
+ end
9
+
10
+ class Comment < ActiveRecord::Base
11
+
12
+ define_index do
13
+ field :full_author
14
+ end
15
+
16
+ def full_author
17
+ "full author name"
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ # Hand modifications will be overwritten.
2
+ indexer {
3
+ mem_limit = 512M
4
+ }
5
+
6
+ searchd {
7
+ listen = 127.0.0.1:3313
8
+ seamless_rotate = 1
9
+ log = <%= Dnif.root_path %>/log/searchd.<%= Dnif.environment %>.log
10
+ query_log = <%= Dnif.root_path %>/log/query.<%= Dnif.environment %>.log
11
+ read_timeout = 5
12
+ max_children = 300
13
+ pid_file = <%= Dnif.root_path %>/log/searchd.<%= Dnif.environment %>.pid
14
+ max_matches = 1000
15
+ }
16
+
17
+ <% sources do |name, model| %>
18
+ source <%= name %>
19
+ {
20
+ type = xmlpipe2
21
+ xmlpipe_command = MODEL=<%= model %> rake dnif:xml --silent
22
+ }
23
+ <% end %>
24
+
25
+ index main
26
+ {
27
+ <% sources do |name| %>
28
+ source = <%= name %>
29
+ <% end %>
30
+ path = <%= Dnif.root_path %>/db/sphinx/<%= Dnif.environment %>/index_main
31
+ docinfo = extern
32
+ morphology = none
33
+ min_word_len = 1
34
+ html_strip = 0
35
+ html_index_attrs =
36
+ charset_type = utf-8
37
+ charset_table = 0..9, A..Z->a..z, -, _, ., &, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F,U+C5->U+E5, U+E5, U+C4->U+E4, U+E4, U+D6->U+F6, U+F6, U+16B, U+0c1->a, U+0c4->a, U+0c9->e, U+0cd->i, U+0d3->o, U+0d4->o, U+0da->u, U+0dd->y, U+0e1->a, U+0e4->a, U+0e9->e, U+0ed->i, U+0f3->o, U+0f4->o, U+0fa->u, U+0fd->y, U+104->U+105, U+105, U+106->U+107, U+10c->c, U+10d->c, U+10e->d, U+10f->d, U+116->U+117, U+117, U+118->U+119, U+11a->e, U+11b->e, U+12E->U+12F, U+12F, U+139->l, U+13a->l, U+13d->l, U+13e->l, U+141->U+142, U+142, U+143->U+144, U+144,U+147->n, U+148->n, U+154->r, U+155->r, U+158->r, U+159->r, U+15A->U+15B, U+15B, U+160->s, U+160->U+161, U+161->s, U+164->t, U+165->t, U+16A->U+16B, U+16B, U+16e->u, U+16f->u, U+172->U+173, U+173, U+179->U+17A, U+17A, U+17B->U+17C, U+17C, U+17d->z, U+17e->z,
38
+ }
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+
3
+ # $:.unshift(File.dirname(__FILE__) + "/../../lib/")
4
+
5
+ require "test/unit"
6
+ require "mocha"
7
+ require "active_record"
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require "dnif"
11
+
12
+ Dnif.root_path = File.expand_path(File.dirname(__FILE__) + "/fixtures")
13
+
14
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
15
+ silence_stream(STDOUT) { load "fixtures/db/schema.rb" }
16
+
17
+ class Post
18
+
19
+ extend Dnif::Search
20
+ end
21
+
22
+ class Comment
23
+
24
+ extend Dnif::Search
25
+ end
26
+
27
+ class User < ActiveRecord::Base
28
+
29
+ define_index do
30
+ field :name
31
+ end
32
+ end
33
+
34
+ class Category < ActiveRecord::Base
35
+ end
36
+
37
+ class Property < ActiveRecord::Base
38
+ end
39
+
40
+ class Person < ActiveRecord::Base
41
+
42
+ define_index do
43
+ field :full_name
44
+ end
45
+
46
+ def full_name
47
+ "#{self.first_name} #{self.last_name}"
48
+ end
49
+ end
50
+
51
+ class Order < ActiveRecord::Base
52
+
53
+ define_index do
54
+ field :buyer
55
+
56
+ where ["ordered_at >= ?", 2.months.ago]
57
+ end
58
+ end
59
+
60
+ class Note < ActiveRecord::Base
61
+
62
+ define_index do
63
+ attribute :clicked, :type => :integer
64
+ attribute :published_at, :type => :datetime
65
+ attribute :expire_at, :type => :date
66
+ attribute :active, :type => :boolean
67
+ attribute :points, :type => :float
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+ require 'erb'
4
+
5
+ class TestConfiguration < Test::Unit::TestCase
6
+
7
+ test "generate configuration from default template" do
8
+ Tilt.expects(:register).with('erb', Tilt::ErubisTemplate)
9
+
10
+ template = mock("Template")
11
+ template.expects(:render).with(Dnif::Configuration).returns('output')
12
+ Tilt.expects(:new).with("config/path.erb").returns(template)
13
+
14
+ file = mock
15
+ file.expects(:puts).with('output')
16
+ File.expects(:open).with(Dnif.root_path + "/config/sphinx/development.conf", "w").yields(file)
17
+
18
+ Dnif::Configuration.generate("config/path.erb")
19
+ end
20
+
21
+ test "sources should iterate over all indexed classes" do
22
+ names = []
23
+ classes = []
24
+ Dnif::Configuration.sources do |name, class_name|
25
+ names << name
26
+ classes << class_name
27
+ end
28
+
29
+ assert_equal ["users_main", "people_main", "orders_main", "notes_main"], names
30
+ assert_equal ["User", "Person", "Order", "Note"], classes
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class TestDnif < Test::Unit::TestCase
5
+
6
+ test ".root_path" do
7
+ Dnif.root_path = "/root/path"
8
+ assert_equal "/root/path", Dnif.root_path
9
+ end
10
+
11
+ test ".environment" do
12
+ assert_equal "development", Dnif.environment
13
+ Dnif.environment = "production"
14
+ assert_equal "production", Dnif.environment
15
+ end
16
+
17
+ test ".models_path" do
18
+ Dnif.models_path = "/models/path"
19
+ assert_equal "/models/path", Dnif.models_path
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class TestIndexBuilder < Test::Unit::TestCase
5
+
6
+ test "object" do
7
+ instance = Object.new
8
+ assert_equal instance, Dnif::IndexBuilder.new(instance, & proc {}).instance_variable_get("@object")
9
+ end
10
+
11
+ test "fields" do
12
+ builder = Dnif::IndexBuilder.new(Object.new, & proc {})
13
+ builder.field(:first_name)
14
+ builder.field(:last_name)
15
+
16
+ assert_equal [:first_name, :last_name], builder.fields
17
+ end
18
+
19
+ test "attribute" do
20
+ builder = Dnif::IndexBuilder.new(Object.new, & proc {})
21
+ builder.attribute(:ordered_at, :type => :datetime)
22
+
23
+ expected = { :ordered_at => :datetime }
24
+ assert_equal expected, builder.attributes
25
+ end
26
+
27
+ test "where" do
28
+ builder = Dnif::IndexBuilder.new(Object.new, & proc {})
29
+ builder.where("status = 'active'")
30
+
31
+ assert_equal "status = 'active'", builder.conditions
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class TestIndexer < Test::Unit::TestCase
5
+
6
+ test "objects without index should not have dnif included" do
7
+ assert_false Post.new.respond_to?(:to_sphinx)
8
+ assert_false Post.respond_to?(:to_sphinx)
9
+ end
10
+
11
+ test "to_sphinx returns a string with sphinx document" do
12
+ comment = Person.create!(:first_name => "Rafael", :last_name => "Souza")
13
+
14
+ expected = "<sphinx:document id=\"6009\">\n <full_name><![CDATA[[Rafael Souza]]></full_name>\n <class_id>336,613,882,1139,1391,1646</class_id>\n</sphinx:document>\n"
15
+ assert_equal expected, comment.to_sphinx
16
+ end
17
+
18
+ test "attributes" do
19
+ note = Note.create!(:clicked => 10, :published_at => (now = DateTime.now), :expire_at => (expire = Date.today + 2.days), :active => true, :points => 1000)
20
+
21
+ expected = "<sphinx:document id=\"2967\">\n <class_id>334,623,884,1125</class_id>\n <clicked>10</clicked>\n <published_at>#{now.to_i}</published_at>\n <expire_at>#{expire.to_datetime.to_i}</expire_at>\n <active>true</active>\n <points>1000.0</points>\n</sphinx:document>\n"
22
+ assert_equal expected, note.to_sphinx
23
+ end
24
+
25
+ test ".to_sphinx should generate a full sphinx xml" do
26
+ comment = Person.create!(:first_name => "Rafael", :last_name => "Souza")
27
+
28
+ expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<sphinx:docset>\n<sphinx:schema>\n <sphinx:field name=\"full_name\"/>\n <sphinx:attr name=\"class_id\" type=\"multi\"/>\n</sphinx:schema>\n#{comment.to_sphinx}</sphinx:docset>"
29
+ assert_equal expected, Person.to_sphinx
30
+ end
31
+
32
+ test "return all indexed classes" do
33
+ assert_equal ["User", "Person", "Order", "Note"], ActiveRecord::Base.classes.keys
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class TestMultiAttribute < Test::Unit::TestCase
5
+
6
+ test ".encode" do
7
+ assert_equal "324,622,873,1126", Dnif::MultiAttribute.encode("Dnif")
8
+ end
9
+
10
+ test ".decode" do
11
+ assert_equal "Dnif", Dnif::MultiAttribute.decode("324,622,873,1126")
12
+ end
13
+
14
+ test ".decode as array" do
15
+ assert_equal "Dnif", Dnif::MultiAttribute.decode(["324", "622", "873", "1126"])
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class TestSearch < Test::Unit::TestCase
5
+
6
+ test "search" do
7
+ results_for_post = {
8
+ :matches => [{
9
+ :doc => 2983,
10
+ :attributes => {
11
+ "class_id" => "336,623,883,1140"
12
+ }
13
+ }]
14
+ }
15
+ results_for_comment = {
16
+ :matches => [{
17
+ :doc => 7893,
18
+ :attributes => {
19
+ "class_id" => "323,623,877,1133,1381,1646,1908"
20
+ }
21
+ }]
22
+ }
23
+
24
+ Riddle::Client.any_instance.expects(:query).times(2).with("post", "*").returns(results_for_post, results_for_comment)
25
+
26
+ Post.expects(:find_all_by_id).once.with([1])
27
+ Comment.expects(:find_all_by_id).once.with([2])
28
+
29
+ Post.search("post")
30
+ Comment.search("post")
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dnif
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ - alpha
10
+ - 2
11
+ version: 0.0.1.alpha.2
12
+ platform: ruby
13
+ authors:
14
+ - Rafael Souza
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-07-01 00:00:00 -03:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activesupport
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: riddle
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ type: :runtime
60
+ version_requirements: *id003
61
+ description: "dnif is a gem to index data using ActiveRecord finders, letting you index your custom methods and not your table fields "
62
+ email: me@rafaelss.com
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files:
68
+ - README.rdoc
69
+ files:
70
+ - README.rdoc
71
+ - Rakefile
72
+ - dnif.gemspec
73
+ - lib/dnif.rb
74
+ - lib/dnif/configuration.rb
75
+ - lib/dnif/index_builder.rb
76
+ - lib/dnif/indexer.rb
77
+ - lib/dnif/multi_attribute.rb
78
+ - lib/dnif/search.rb
79
+ - lib/dnif/tasks.rb
80
+ - test/fixtures/db/schema.rb
81
+ - test/fixtures/log/searchd.pid
82
+ - test/fixtures/models.rb
83
+ - test/fixtures/templates/config.erb
84
+ - test/test_helper.rb
85
+ - test/unit/test_configuration.rb
86
+ - test/unit/test_dnif.rb
87
+ - test/unit/test_index_builder.rb
88
+ - test/unit/test_indexer.rb
89
+ - test/unit/test_multi_attribute.rb
90
+ - test/unit/test_search.rb
91
+ has_rdoc: true
92
+ homepage: http://github.com/rafaelss/dnif
93
+ licenses: []
94
+
95
+ post_install_message:
96
+ rdoc_options:
97
+ - --charset=UTF-8
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">"
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 1
115
+ - 3
116
+ - 1
117
+ version: 1.3.1
118
+ requirements: []
119
+
120
+ rubyforge_project:
121
+ rubygems_version: 1.3.7
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: dnif is the new find... for sphinx
125
+ test_files:
126
+ - test/fixtures/db/schema.rb
127
+ - test/fixtures/models.rb
128
+ - test/test_helper.rb
129
+ - test/unit/test_configuration.rb
130
+ - test/unit/test_dnif.rb
131
+ - test/unit/test_index_builder.rb
132
+ - test/unit/test_indexer.rb
133
+ - test/unit/test_multi_attribute.rb
134
+ - test/unit/test_search.rb