Skip to content

Commit

Permalink
Using builder gem's BlankSlate.
Browse files Browse the repository at this point in the history
  • Loading branch information
pat committed Sep 2, 2011
1 parent 14cee4c commit a924c1e
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 69 deletions.
1 change: 1 addition & 0 deletions HISTORY
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Edge:
* Bringing in builder gem's BlankSlate for our Builder - better at keeping global Rake methods out of the way.
* Use PostgreSQL's array_agg function if it exists, instead of array_accum (PG v8.4 or newer).
* Shuffle multiple Sphinx addresses by default, but allow that to be turned off (Ngan Pham).
* Fix string attributes when using Sphinx 2.0.1 and bin_path.
Expand Down
133 changes: 64 additions & 69 deletions lib/thinking_sphinx/index/builder.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'blankslate'

module ThinkingSphinx
class Index
# The Builder class is the core for the index definition block processing.
Expand All @@ -11,47 +13,40 @@ class Index
# your indexes. #where provides a method to add manual SQL conditions, and
# set_property allows you to set some settings on a per-index basis. Check
# out each method's documentation for better ideas of usage.
#
class Builder
instance_methods.grep(/^[^_]/).each { |method|
next if method.to_s == "instance_eval"
define_method(method) {
caller.grep(/irb.completion/).empty? ? method_missing(method) : super
}
}

#
class Builder < BlankSlate
def self.generate(model, name = nil, &block)
index = ThinkingSphinx::Index.new(model)
index.name = name unless name.nil?

Builder.new(index, &block) if block_given?

index.delta_object = ThinkingSphinx::Deltas.parse index
index
end

def initialize(index, &block)
@index = index
@explicit_source = false

self.instance_eval &block

if no_fields?
raise "At least one field is necessary for an index"
end
end

def define_source(&block)
if @explicit_source
@source = ThinkingSphinx::Source.new(@index)
@index.sources << @source
else
@explicit_source = true
end

self.instance_eval &block
end

# This is how you add fields - the strings Sphinx looks at - to your
# index. Technically, to use this method, you need to pass in some
# columns and options - but there's some neat method_missing stuff
Expand All @@ -63,34 +58,34 @@ def define_source(&block)
# field.
#
# Adding Single-Column Fields:
#
#
# You can use symbols or methods - and can chain methods together to
# get access down the associations tree.
#
#
# indexes :id, :as => :my_id
# indexes :name, :sortable => true
# indexes first_name, last_name, :sortable => true
# indexes users.posts.content, :as => :post_content
# indexes users(:id), :as => :user_ids
#
# Keep in mind that if any keywords for Ruby methods - such as id or
# Keep in mind that if any keywords for Ruby methods - such as id or
# name - clash with your column names, you need to use the symbol
# version (see the first, second and last examples above).
#
# If you specify multiple columns (example #2), a field will be created
# for each. Don't use the :as option in this case. If you want to merge
# those columns together, continue reading.
#
#
# Adding Multi-Column Fields:
#
#
# indexes [first_name, last_name], :as => :name
# indexes [location, parent.location], :as => :location
#
# To combine multiple columns into a single field, you need to wrap
# them in an Array, as shown by the above examples. There's no
# limitations on whether they're symbols or methods or what level of
# associations they come from.
#
#
# Adding SQL Fragment Fields
#
# You can also define a field using an SQL fragment, useful for when
Expand All @@ -102,106 +97,106 @@ def indexes(*args)
options = args.extract_options!
args.each do |columns|
field = Field.new(source, FauxColumn.coerce(columns), options)

add_sort_attribute field, options if field.sortable
add_facet_attribute field, options if field.faceted
end
end

# This is the method to add attributes to your index (hence why it is
# aliased as 'attribute'). The syntax is the same as #indexes, so use
# that as starting point, but keep in mind the following points.
#
#
# An attribute can have an alias (the :as option), but it is always
# sortable - so you don't need to explicitly request that. You _can_
# specify the data type of the attribute (the :type option), but the
# code's pretty good at figuring that out itself from peering into the
# database.
#
#
# Attributes are limited to the following types: integers, floats,
# datetimes (converted to timestamps), booleans, strings and MVAs
# (:multi). Don't forget that Sphinx converts string attributes to
# integers, which are useful for sorting, but that's about it.
#
#
# Collection of integers are known as multi-value attributes (MVAs).
# Generally these would be through a has_many relationship, like in this
# example:
#
#
# has posts(:id), :as => :post_ids
#
#
# This allows you to filter on any of the values tied to a specific
# record. Might be best to read through the Sphinx documentation to get
# a better idea of that though.
#
#
# Adding SQL Fragment Attributes
#
# You can also define an attribute using an SQL fragment, useful for
# when you would like to index a calculated value. Don't forget to set
# the type of the attribute though:
#
# has "age < 18", :as => :minor, :type => :boolean
#
#
# If you're creating attributes for latitude and longitude, don't
# forget that Sphinx expects these values to be in radians.
#
#
def has(*args)
options = args.extract_options!
args.each do |columns|
attribute = Attribute.new(source, FauxColumn.coerce(columns), options)

add_facet_attribute attribute, options if attribute.faceted
end
end

def facet(*args)
options = args.extract_options!
options[:facet] = true

args.each do |columns|
attribute = Attribute.new(source, FauxColumn.coerce(columns), options)

add_facet_attribute attribute, options
end
end

def join(*args)
args.each do |association|
Join.new(source, association)
end
end

# Use this method to add some manual SQL conditions for your index
# request. You can pass in as many strings as you like, they'll get
# joined together with ANDs later on.
#
#
# where "user_id = 10"
# where "parent_type = 'Article'", "created_at < NOW()"
#
#
def where(*args)
source.conditions += args
end

# Use this method to add some manual SQL strings to the GROUP BY
# clause. You can pass in as many strings as you'd like, they'll get
# joined together with commas later on.
#
#
# group_by "lat", "lng"
#
#
def group_by(*args)
source.groupings += args
end

# This is what to use to set properties on the index. Chief amongst
# those is the delta property - to allow automatic updates to your
# indexes as new models are added and edited - but also you can
# define search-related properties which will be the defaults for all
# searches on the model.
#
#
# set_property :delta => true
# set_property :field_weights => {"name" => 100}
# set_property :order => "name ASC"
# set_property :select => 'name'
#
#
# Also, the following two properties are particularly relevant for
# geo-location searching - latitude_attr and longitude_attr. If your
# attributes for these two values are named something other than
Expand All @@ -210,67 +205,67 @@ def group_by(*args)
# geo-related search.
#
# set_property :latitude_attr => "lt", :longitude_attr => "lg"
#
#
# Please don't forget to add a boolean field named 'delta' to your
# model's database table if enabling the delta index for it.
# Valid options for the delta property are:
#
#
# true
# false
# :default
# :delayed
# :datetime
#
# You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
#
# You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
# your own handling for delta indexing.
#
#
def set_property(*args)
options = args.extract_options!
options.each do |key, value|
set_single_property key, value
end

set_single_property args[0], args[1] if args.length == 2
end
alias_method :set_properties, :set_property

# Handles the generation of new columns for the field and attribute
# definitions.
#
#
def method_missing(method, *args)
FauxColumn.new(method, *args)
end

# A method to allow adding fields from associations which have names
# that clash with method names in the Builder class (ie: properties,
# fields, attributes).
#
#
# Example: indexes assoc(:properties).column
#
#
def assoc(assoc, *args)
FauxColumn.new(assoc, *args)
end

# Use this method to generate SQL for your attributes, conditions, etc.
# You can pass in as whatever ActiveRecord::Base.sanitize_sql accepts.
#
#
# where sanitize_sql(["active = ?", true])
# #=> WHERE active = 1
#
#
def sanitize_sql(*args)
@index.model.send(:sanitize_sql, *args)
end

private

def source
@source ||= begin
source = ThinkingSphinx::Source.new(@index)
@index.sources << source
source
end
end

def set_single_property(key, value)
source_options = ThinkingSphinx::Configuration::SourceOptions
if source_options.include?(key.to_s)
Expand All @@ -279,19 +274,19 @@ def set_single_property(key, value)
@index.local_options.merge! key => value
end
end

def add_sort_attribute(field, options)
add_internal_attribute field, options, "_sort"
end

def add_facet_attribute(property, options)
add_internal_attribute property, options, "_facet", true
@index.model.sphinx_facets << property.to_facet
end

def add_internal_attribute(property, options, suffix, crc = false)
return unless ThinkingSphinx::Facet.translate?(property)

Attribute.new(source,
property.columns.collect { |col| col.clone },
options.merge(
Expand All @@ -301,7 +296,7 @@ def add_internal_attribute(property, options, suffix, crc = false)
).except(:facet)
)
end

def no_fields?
@index.sources.empty? || @index.sources.any? { |source|
source.fields.length == 0
Expand Down
1 change: 1 addition & 0 deletions thinking-sphinx.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'activerecord', ['< 3.0.0', '>= 1.15.6']
s.add_runtime_dependency 'after_commit', '>= 1.0.7'
s.add_runtime_dependency 'riddle', '>= 1.3.3'
s.add_runtime_dependency 'builder', '>= 2.1.2'

s.add_development_dependency 'cucumber', '1.0.2'
s.add_development_dependency 'faker', '0.3.1'
Expand Down

0 comments on commit a924c1e

Please sign in to comment.