mongoid-giza 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,19 @@
1
1
  module Mongoid
2
2
  module Giza
3
-
4
- # Defines a dynamic index which is used to generate a index for each object of the class
3
+ # Defines a dynamic index which is used to generate a index for each object
4
+ # of the class
5
5
  class DynamicIndex
6
6
  attr_reader :klass, :settings, :block
7
7
 
8
8
  # Creates a new dynamic index for the supplied class
9
9
  #
10
- # @param klass [Class] a class which each object will generate an {Mongoid::Giza::Index}
10
+ # @param klass [Class] a class which each object will generate an
11
+ # {Mongoid::Giza::Index}
11
12
  # after the evaluation of the block
12
- # @param settings [Hash] a hash of settings to be defined on every generated index
13
- # @param block [Proc] the routine that will be evaluated for each object from the class
13
+ # @param settings [Hash] a hash of settings to be defined on every
14
+ # generated index
15
+ # @param block [Proc] the routine that will be evaluated for each object
16
+ # from the class
14
17
  def initialize(klass, settings, block)
15
18
  @klass = klass
16
19
  @settings = settings
@@ -21,29 +24,32 @@ module Mongoid
21
24
  # The name of the index is unique so in case of a name collision,
22
25
  # the last index to be generated is the one that will persist
23
26
  #
24
- # @return [Hash<Symbol, Mongoid::Giza::Index>] an hash with every key being the index name
27
+ # @return [Hash<Symbol, Mongoid::Giza::Index>] an hash with every key
28
+ # being the index name
25
29
  # and the value the index itself
26
30
  def generate!
27
31
  indexes = {}
28
32
  klass.all.each do |object|
29
33
  index = generate_index(object)
30
- indexes[index.name] = index if !index.nil?
34
+ indexes[index.name] = index if index
31
35
  end
32
36
  indexes
33
37
  end
34
38
 
35
39
  # Generates the index for the object passed as parameter.
36
- # It is only generated if the object's class is the class or a subclass of the index's class
40
+ # It is only generated if the object's class is the class or a subclass of
41
+ # the index's class
37
42
  #
38
- # @param object [Mongoid::Document] the object which the index block wil be evaluated for
43
+ # @param object [Mongoid::Document] the object which the index block wil
44
+ # be evaluated for
39
45
  #
40
- # @return [Mongoid::Giza::Index, NilClass] the resulting index from the evaluation
41
- # or nil if the object's class is not the index's class or a subclass of it
46
+ # @return [Mongoid::Giza::Index, NilClass] the resulting index from the
47
+ # evaluation or nil if the object's class is not the index's class or a
48
+ # subclass of it
42
49
  def generate_index(object)
43
- if object.is_a?(klass)
44
- index = Mongoid::Giza::Index.new(klass, settings)
45
- Docile.dsl_eval(index, object, &block)
46
- end
50
+ return unless object.is_a?(klass)
51
+ index = Mongoid::Giza::Index.new(klass, settings)
52
+ Docile.dsl_eval(index, object, &block)
47
53
  end
48
54
  end
49
55
  end
@@ -1,9 +1,7 @@
1
1
  module Mongoid
2
2
  module Giza
3
-
4
3
  # Represents a Sphinx index
5
4
  class Index
6
-
7
5
  # Hash in which each key is a class accepted by +Mongoid+
8
6
  # and its value is a compatible Sphix attribute type
9
7
  TYPES_MAP = {
@@ -17,21 +15,24 @@ module Mongoid
17
15
  Time => :timestamp,
18
16
  BigDecimal => :float,
19
17
  Float => :float,
20
- Array => :multi,
18
+ Array => :json,
21
19
  Range => :multi,
22
20
  Hash => :json,
23
- Moped::BSON::ObjectId => :string,
24
- ActiveSupport::TimeWithZone => :timestamp,
21
+ BSON::ObjectId => :string,
22
+ ActiveSupport::TimeWithZone => :timestamp
25
23
  }
26
24
 
27
25
  attr_accessor :klass, :settings, :fields, :attributes
28
26
 
29
- # Creates a new index with a class, which should include Mongoid::Document, and an optional settings hash.
27
+ # Creates a new index with a class, which should include
28
+ # Mongoid::Document, and an optional settings hash.
30
29
  #
31
- # Note that no validations are made on class, so classes that behave like Mongoid::Document should be fine.
30
+ # Note that no validations are made on class, so classes that behave like
31
+ # Mongoid::Document should be fine.
32
32
  #
33
33
  # @param klass [Class] the class whose objects will be indexed
34
- # @param settings [Hash] an optional settings hash to be forwarded to Riddle
34
+ # @param settings [Hash] an optional settings hash to be forwarded to
35
+ # Riddle
35
36
  def initialize(klass, settings = {})
36
37
  @klass = klass
37
38
  @settings = settings
@@ -43,38 +44,48 @@ module Mongoid
43
44
 
44
45
  # Adds a full-text field to the index with the corresponding name
45
46
  #
46
- # If a block is given then it will be evaluated for each instance of the class being indexed
47
- # and the resulting string will be the field value.
48
- # Otherwise the field value will be the value of the corresponding object field
47
+ # If a block is given then it will be evaluated for each instance of the
48
+ # class being indexed and the resulting string will be the field value.
49
+ # Otherwise the field value will be the value of the corresponding object
50
+ # field
49
51
  #
50
52
  # @param name [Symbol] the name of the field
51
53
  # @param options [Hash] options for the field.
52
- # @option options [TrueClass, FalseClass] :attribute whether the field will also be a attribute or not (see {Mongoid::Giza::Index::Field#initialize})
53
- # @param block [Proc] an optional block to be evaluated at the scope of the document on index creation
54
+ # @option options [TrueClass, FalseClass] :attribute whether the field
55
+ # will also be a attribute or not (see
56
+ # {Mongoid::Giza::Index::Field#initialize})
57
+ # @param block [Proc] an optional block to be evaluated at the scope of
58
+ # the document on index creation
54
59
  def field(name, options = {}, &block)
55
- attribute = options[:attribute].nil? ? false : true
56
- @fields << Mongoid::Giza::Index::Field.new(name, attribute, &block)
60
+ attribute = options[:attribute]
61
+ @fields << Field.new(name, attribute, &block)
57
62
  end
58
63
 
59
64
  # Adds an attribute to the index with the corresponding name.
60
65
  #
61
- # If a type is not given then it will try to fetch the type of the corresponding class field,
62
- # falling back to :string
66
+ # If a type is not given then it will try to fetch the type of the
67
+ # corresponding class field, falling back to :string
63
68
  #
64
- # If a block is given then it will be evaluated for each instance of the class being indexed
65
- # and the resulting value will be the attribute value.
66
- # Otherwise the attribute value will be the value of the corresponding object field
69
+ # If a block is given then it will be evaluated for each instance of the
70
+ # class being indexed and the resulting value will be the attribute
71
+ # value.
72
+ # Otherwise the attribute value will be the value of the corresponding
73
+ # object field
67
74
  #
68
75
  # @param name [Symbol] the name of the attribute
69
76
  # @param type [Symbol] an optional attribute type
70
- # @param block [Proc] an optional block to be evaluated at the scope of the document on index creation
71
- def attribute(name, type = nil, &block)
72
- if type.nil?
77
+ # @param block [Proc] an optional block to be evaluated at the scope of
78
+ # the document on index creation
79
+ def attribute(name, type = nil, options = {}, &block)
80
+ unless type
73
81
  field = @klass.fields[name.to_s]
74
- type = field.nil? ? Mongoid::Giza::Index::TYPES_MAP.values.first :
75
- Mongoid::Giza::Index::TYPES_MAP[field.type] || Mongoid::Giza::Index::TYPES_MAP.values.first
82
+ if field
83
+ type = TYPES_MAP[field.type] || :string
84
+ else
85
+ type = :string
86
+ end
76
87
  end
77
- @attributes << Mongoid::Giza::Index::Attribute.new(name, type, &block)
88
+ @attributes << Attribute.new(name, type, options, &block)
78
89
  end
79
90
 
80
91
  # Retrieves and optionally sets the index name
@@ -83,22 +94,25 @@ module Mongoid
83
94
  #
84
95
  # @return [Symbol] The name of the index
85
96
  def name(new_name = nil)
86
- @name = new_name.to_sym if !new_name.nil?
97
+ @name = new_name.to_sym if new_name
87
98
  @name
88
99
  end
89
100
 
90
- # Defines the Mongoid::Criteria that will be used to retrive objects when indexing.
101
+ # Defines the Mongoid::Criteria that will be used to retrive objects when
102
+ # indexing.
91
103
  # Use this to filter what objects from the class will be indexed.
92
104
  # When an index is created the criteria is defined as class.all
93
105
  #
94
- #@param new_criteria [Mongoid::Criteria] the criteria to be used
106
+ # @param new_criteria [Mongoid::Criteria] the criteria to be used
95
107
  def criteria(new_criteria = nil)
96
108
  @criteria = new_criteria || @criteria
97
109
  end
98
110
 
99
- # Generates a XML document according to the XMLPipe2 specification from Sphinx
111
+ # Generates a XML document according to the XMLPipe2 specification from
112
+ # Sphinx
100
113
  #
101
- # @param buffer [#<<] any IO object that supports appending content using <<
114
+ # @param buffer [#<<] any IO object that supports appending content using
115
+ # <<
102
116
  def xmlpipe2(buffer)
103
117
  Mongoid::Giza::XMLPipe2.new(self, buffer).generate!
104
118
  end
@@ -1,37 +1,42 @@
1
1
  module Mongoid
2
2
  module Giza
3
3
  class Index
4
-
5
- # Represents an Sphinx index {http://sphinxsearch.com/docs/current.html#attributes attribute}
4
+ # Represents a Sphinx index attribute
6
5
  class Attribute
7
-
8
6
  # Defines the array of currently supported Sphix attribute types
9
7
  TYPES = [
10
- :uint, :bool, :bigint, :timestamp, :str2ordinal,
11
- :float, :multi, :string, :json, :str2wordcount
8
+ :uint, :bool, :bigint, :timestamp, :float,
9
+ :multi, :multi_64, :string, :json
12
10
  ]
13
11
 
14
- attr_accessor :name, :type, :block
12
+ attr_accessor :name, :type, :default, :bits, :block
15
13
 
16
14
  # Creates a new attribute with name, type and an optional block
17
15
  #
18
- # If a block is given then it will be evaluated for each instance of the class being indexed
19
- # and the resulting value will be the attribute value.
20
- # Otherwise the attribute value will be the value of the corresponding object field
16
+ # If a block is given then it will be evaluated for each instance of the
17
+ # class being indexed and the resulting value will be the attribute
18
+ # value.
19
+ # Otherwise the attribute value will be the value of the corresponding
20
+ # object field
21
21
  #
22
22
  # @param name [Symbol] the name of the attribute
23
- # @param type [Symbol] the type of the attribute. Must be one of the types defined in {Mongoid::Giza::Index::Attribute::TYPES}
24
- # @param block [Proc] an optional block to be evaluated at the scope of the document on index creation
23
+ # @param type [Symbol] the type of the attribute. Must be one of the
24
+ # types defined in {Mongoid::Giza::Index::Attribute::TYPES}
25
+ # @param block [Proc] an optional block to be evaluated at the scope of
26
+ # the document on index creation
25
27
  #
26
- # @raise [TypeError] if the type is not valid. (see {Mongoid::Giza::Index::Attribute::TYPES})
27
- def initialize(name, type, &block)
28
- raise TypeError,
29
- "attribute type not supported. " \
30
- "It must be one of the following: " \
31
- "#{Mongoid::Giza::Index::Attribute::TYPES.join(", ")}" unless Mongoid::Giza::Index::Attribute::TYPES.include? type
28
+ # @raise [TypeError] if the type is not valid. (see
29
+ # {Mongoid::Giza::Index::Attribute::TYPES})
30
+ def initialize(name, type, options = {}, &block)
31
+ fail TypeError,
32
+ "Attribute type not supported. " \
33
+ "It must be one of the following: " \
34
+ "#{TYPES.join(', ')}" unless TYPES.include? type
32
35
  @name = name.to_s.mb_chars.downcase.to_sym
33
36
  @type = type
34
37
  @block = block
38
+ @default = options[:default]
39
+ @bits = options[:bits] if type == :uint
35
40
  end
36
41
  end
37
42
  end
@@ -1,22 +1,24 @@
1
1
  module Mongoid
2
2
  module Giza
3
3
  class Index
4
-
5
- # Represents a Sphinx index {http://sphinxsearch.com/docs/current.html#fields full-text field}
4
+ # Represents a Sphinx indexed field
6
5
  class Field
7
6
  attr_accessor :name, :attribute, :block
8
7
 
9
8
  # Creates a full-text field with a name and an optional block
10
9
  #
11
- # If a block is given then it will be evaluated for each instance of the class being indexed
10
+ # If a block is given then it will be evaluated for each instance of the
11
+ # class being indexed
12
12
  # and the resulting string will be the field value.
13
- # Otherwise the field value will be the value of the corresponding object field
13
+ # Otherwise the field value will be the value of the corresponding
14
+ # object field
14
15
  #
15
16
  # @param name [Symbol] the name of the field
16
- # @param attribute [TrueClass, FalseClass] whether this field will also be stored as an string attribute
17
- # (see {http://sphinxsearch.com/docs/current.html#conf-xmlpipe-field-string})
18
- # @param block [Proc] an optional block to be evaluated at the scope of the document on index creation
19
- def initialize(name, attribute = false, &block)
17
+ # @param attribute [TrueClass, FalseClass] whether this field will also
18
+ # be stored as an string attribute
19
+ # @param block [Proc] an optional block to be evaluated at the scope of
20
+ # the document on index creation
21
+ def initialize(name, attribute = nil, &block)
20
22
  @name = name.to_s.mb_chars.downcase.to_sym
21
23
  @attribute = attribute
22
24
  @block = block
@@ -1,6 +1,5 @@
1
1
  module Mongoid
2
2
  module Giza
3
-
4
3
  # Routines related to creating the defined indexes in sphinx
5
4
  class Indexer
6
5
  include Singleton
@@ -8,13 +7,14 @@ module Mongoid
8
7
  # Creates the Indexer instance
9
8
  def initialize
10
9
  @configuration = Mongoid::Giza::Configuration.instance
11
- @controller = Riddle::Controller.new(@configuration, @configuration.file.output_path)
10
+ @controller = Riddle::Controller.new(@configuration,
11
+ @configuration.file.output_path)
12
12
  end
13
13
 
14
14
  # Index everything, regenerating all dynamic indexes from all classes
15
15
  def full_index!
16
16
  @configuration.clear_generated_indexes
17
- giza_classes.each { |klass| klass.regenerate_sphinx_indexes }
17
+ giza_classes.each(&:regenerate_sphinx_indexes)
18
18
  @configuration.render
19
19
  index!
20
20
  end
@@ -23,13 +23,16 @@ module Mongoid
23
23
  #
24
24
  # @param names [Array<Symbol>] name of the indexes that should be indexed.
25
25
  # If not provided all indexes from the configuration file are indexed
26
- # @param options [Hash] additional options to pass to Riddle::Controller#index
27
- # @option options [TrueClass, FalseClass] :verbose shows the indexer output
26
+ # @param options [Hash] additional options to pass to
27
+ # Riddle::Controller#index
28
+ # @option options [TrueClass, FalseClass] :verbose shows the indexer
29
+ # output
28
30
  def index!(*names)
29
31
  @controller.index(*names)
30
32
  end
31
33
 
32
- # @return [Array<Class>] all Mongoid models that include the {Mongoid::Giza} module
34
+ # @return [Array<Class>] all Mongoid models that include the
35
+ # {Mongoid::Giza} module
33
36
  def giza_classes
34
37
  Mongoid.models.select { |model| model.include?(Mongoid::Giza) }
35
38
  end
@@ -1,24 +1,22 @@
1
1
  module Mongoid
2
2
  module Giza
3
-
4
3
  # MongoDB counter collection to generate ids compatible with sphinx
5
- class GizaID
4
+ class ID
6
5
  include Mongoid::Document
7
6
 
8
7
  field :_id, type: Symbol
9
8
  field :seq, type: Integer, default: 0
10
9
 
11
- attr_accessible :id
12
-
13
10
  class << self
14
-
15
11
  # Gets the next id in the sequence to assign to an object
16
12
  #
17
- # @param klass [Symbol] the name of the class which next id will be retrived for
13
+ # @param klass [Symbol] the name of the class which next id will be
14
+ # retrived for
18
15
  #
19
16
  # @return [Integer] the next id in the sequence
20
- def next_id(klass)
21
- giza_id = where(id: klass).find_and_modify({"$inc" => {seq: 1}}, new: true)
17
+ def next(klass)
18
+ giza_id = where(id: klass).find_and_modify({"$inc" => {seq: 1}},
19
+ new: true)
22
20
  giza_id.seq
23
21
  end
24
22
  end
@@ -2,15 +2,18 @@ require "rails"
2
2
 
3
3
  module Mongoid
4
4
  module Giza
5
+ # :nodoc:
5
6
  class Railtie < Rails::Railtie
6
7
  configuration = Mongoid::Giza::Configuration.instance
7
8
 
8
9
  initializer "mongoid-giza.load-configuration" do
9
10
  # Sets the default xmlpipe_command
10
- configuration.source.xmlpipe_command = "rails r '<%= index.klass %>.sphinx_indexes[:<%= index.name %>].xmlpipe2(STDOUT)'"
11
+ configuration.source.xmlpipe_command =
12
+ "rails r '<%= index.klass %>.sphinx_indexes[:<%= index.name %>]" \
13
+ ".xmlpipe2(STDOUT)'"
11
14
  # Loads the configuration file
12
- file = Rails.root.join("config", "giza.yml")
13
- configuration.load(file, Rails.env) if file.file?
15
+ giza_yml = Rails.root.join("config", "giza.yml")
16
+ configuration.load(giza_yml, Rails.env) if giza_yml.file?
14
17
  end
15
18
  end
16
19
  end
@@ -1,82 +1,96 @@
1
1
  module Mongoid
2
2
  module Giza
3
-
4
3
  # Executes queries on Sphinx
5
4
  class Search
6
- attr_accessor :indexes
5
+ attr_accessor :indexes, :query_string
7
6
  attr_reader :client
8
7
 
8
+ alias_method :fulltext, :query_string=
9
+
9
10
  # Creates a new search
10
11
  #
11
12
  # @param host [String] the host address of sphinxd
12
13
  # @param port [Fixnum] the TCP port of sphinxd
13
- # @param names [Array] an optional array defining the indexes that the search will run on.
14
+ # @param names [Array] an optional array defining the indexes that the
15
+ # search will run on.
14
16
  # Defaults to "[]" which means all indexes
15
17
  def initialize(host, port, names = [])
16
18
  @client = Riddle::Client.new(host, port)
17
19
  @indexes = names
18
20
  end
19
21
 
20
- # Sets the search criteria on full-text fields
21
- #
22
- # @param query [String] a sphinx query string based on the current {http://sphinxsearch.com/docs/current.html#matching-modes matching mode}
23
- def fulltext(query)
24
- index = indexes.length > 0 ? indexes.join(" ") : "*"
25
- @client.append_query(query, index)
26
- end
27
-
28
22
  # Sets a filter based on an attribute.
29
- # Only documents that the attribute value matches will be returned from the search
23
+ # Only documents that the attribute value matches will be returned from
24
+ # the search
30
25
  #
31
26
  # @param attribute [Symbol] the attribute name to set the filter
32
- # @param value [Fixnum, Float, Range] the value (or values) that the attribute must match
27
+ # @param value [Fixnum, Float, Range] the value (or values) that the
28
+ # attribute must match
33
29
  def with(attribute, value)
34
- @client.filters << Riddle::Client::Filter.new(attribute.to_s, value, false)
30
+ @client.filters << Riddle::Client::Filter.new(attribute.to_s, value,
31
+ false)
35
32
  end
36
33
 
37
34
  # Excludes from the search documents that the attribute value matches
38
35
  #
39
36
  # @param attribute [Symbol] the attribute name
40
- # @param value [Fixnum, Float, Range] the value (or values) that the attribute must match
37
+ # @param value [Fixnum, Float, Range] the value (or values) that the
38
+ # attribute must match
41
39
  def without(attribute, value)
42
- @client.filters << Riddle::Client::Filter.new(attribute.to_s, value, true)
40
+ @client.filters << Riddle::Client::Filter.new(attribute.to_s, value,
41
+ true)
43
42
  end
44
43
 
45
44
  # Sets the order in which the results will be returned
46
45
  #
47
46
  # @param attribute [Symbol] the attribute used for sorting
48
- # @param order [Symbol] the order of the sorting. Valid values are :asc and :desc
47
+ # @param order [Symbol] the order of the sorting. Valid values are :asc
48
+ # and :desc
49
49
  def order_by(attribute, order)
50
50
  @client.sort_by = "#{attribute} #{order.to_s.upcase}"
51
51
  end
52
52
 
53
- # Executes the configured queries
53
+ # Executes the configured query
54
54
  #
55
55
  # @return [Array] an Array of Hashes as specified by Riddle::Response
56
56
  def run
57
- @client.run
57
+ index = indexes.length > 0 ? indexes.join(" ") : "*"
58
+ @client.query(query_string, index)
58
59
  end
59
60
 
60
61
  # Checks for methods on Riddle::Client
61
62
  #
62
- # @param method [Symbol, String] the method name that will be checked on Riddle::Client
63
+ # @param method [Symbol, String] the method name that will be checked on
64
+ # Riddle::Client
63
65
  #
64
- # @return [TrueClass, FalseClass] true if either Riddle::Client or Mongoid::Giza::Search respond to the method
66
+ # @return [TrueClass, FalseClass] true if either Riddle::Client or
67
+ # Mongoid::Giza::Search respond to the method
65
68
  def respond_to?(method)
66
- @client.respond_to?("#{method}=") || super
69
+ @client.respond_to?(method) ||
70
+ @client.respond_to?("#{method}=") ||
71
+ super
67
72
  end
68
73
 
69
- # Dynamically dispatches the method call to Riddle::Client if the method is defined in it
74
+ # Dynamically dispatches the method call to Riddle::Client if the method
75
+ # is defined in it
70
76
  #
71
- # @param method [Symbol, String] the method name that will be called on Riddle::Client
72
- # @param args [Array] an argument list that will also be forwarded to the Riddle::Client method
77
+ # @param method [Symbol, String] the method name that will be called on
78
+ # Riddle::Client
79
+ # @param args [Array] an argument list that will also be forwarded to the
80
+ # Riddle::Client method
73
81
  #
74
82
  # @return [Object] the return value of the Riddle::Client method
75
83
  #
76
84
  # @raise [NoMethodError] if the method is also missing on Riddle::Client
77
85
  def method_missing(method, *args)
78
- super if !respond_to?(method)
79
- @client.send "#{method}=", *args
86
+ if args.length == 1
87
+ method_writer = "#{method}="
88
+ super unless respond_to?(method_writer)
89
+ @client.send method_writer, *args
90
+ else
91
+ super unless respond_to?(method)
92
+ @client.send method, *args
93
+ end
80
94
  end
81
95
  end
82
96
  end