Class: Solrizer::FieldMapper

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/solrizer/field_mapper.rb

Overview

Maps Term names and values to Solr fields, based on the Term’s data type and any index_as options.

The basic structure of a mapper is:

Mapping on Index Type

To define a custom mapper:

class CustomMapper < Solrizer::FieldMapper
  index_as :searchable, :suffix => '_search'
  index_as :edible,     :suffix => '_food'
end

#   t.dish_name   :index_as => [:searchable]            -maps to->   dish_name_search
#   t.ingredients :index_as => [:searchable, :edible]   -maps to->   ingredients_search, ingredients_food

(See Solrizer::XML::TerminologyBasedSolrizer for instructions on applying a custom mapping once you have defined it.)

Default Index Types

You can mark a particular index type as a default. It will then always be included unless terms explicity exclude it with the “not_” prefix:

class CustomMapper < Solrizer::FieldMapper
  index_as :searchable, :suffix => '_search', :default => true
  index_as :edible,     :suffix => '_food'
end

#   t.dish_name                                                   -maps to->   dish_name_search
#   t.ingredients :index_as => [:edible]                          -maps to->   ingredients_search, ingredients_food
#   t.secret_ingredients :index_as => [:not_searchable, :edible]  -maps to->   secret_ingredients_food

Mapping on Data Type

A mapper can apply different suffixes based on a term’s data type:

class CustomMapper < Solrizer::FieldMapper
  index_as :searchable, :suffix => '_search' do |type|
    type.date    :suffix => '_date'
    type.integer :suffix => '_numeric'
    type.float   :suffix => '_numeric'
  end
  index_as :edible, :suffix => '_food'
end

#   t.published   :type => :date, :index_as => [:searchable]     -maps to->   published_date
#   t.votes       :type => :integer, :index_as => [:searchable]  -maps to->   votes_numeric

If a specific data type doesn’t appear in the list, the mapper falls back to the index_as:

#   t.description :type => :text, :index_as => [:searchable]     -maps to->   description_search

Custom Value Converters

All of the above applies to the generation of Solr names. Mappers can also provide custom conversion logic for the generation of Solr values by attaching a custom value converter block to a data type:

require 'time'

class CustomMapper < Solrizer::FieldMapper
  index_as :searchable, :suffix => '_search' do |type|
    type.date do |value|
      Time.parse(value).utc.to_i
    end
  end
end

Note that the nesting order is always:

FieldMapper definition
  index_as
    data type
      value converter

You can use the special data type “default” to apply custom value conversion to any data type:

require 'time'

class CustomMapper < Solrizer::FieldMapper
  index_as :searchable do |type|
    type.date :suffix => '_date' do |value|
      Time.parse(value).utc.to_i
    end
    type.default :suffix => '_search' do |value|
      value.to_s.strip
    end
  end
end

This example converts searchable dates to milliseconds, and strips extra whitespace from all other searchable data types.

Note that the :suffix option may appear on the data types and the index_as. The search order for the suffix on a field of type foo is:

  1. type.foo

  2. type.default

  3. index_as

The suffix is optional in all three places.

Note that a single Term with multiple index types can translate into multiple Solr fields, because we may want Solr to index a single field in multiple ways. However, if two different mappings generate both the same solr field name and the same value, the mapper will only emit a single field.

ID Field

In addition to the normal field mappings, Solrizer gives special treatment to an ID field. If you want that logic (and you probably do), specify a name for this field:

class CustomMapper < Solrizer::FieldMapper
  id_field 'id' 
end

Extending the Default

The default mapper is Solrizer::FieldMapper::Default. You can customize the default mapping by subclassing it. For example, to override the ID field name and the default suffix for sortable, and inherit everything else:

class CustomMapperBasedOnDefault < Solrizer::FieldMapper::Default
  id_field 'guid'
  index_as :sortable, :suffix => '_xsort'
end

Direct Known Subclasses

Default

Defined Under Namespace

Classes: DataTypeMapping, DataTypeMappingBuilder, Default, IndexTypeMapping

Constant Summary collapse

@@instance_init_actions =

—— Class methods ——

Hash.new { |h,k| h[k] = [] }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFieldMapper

Returns a new instance of FieldMapper.



219
220
221
222
223
# File 'lib/solrizer/field_mapper.rb', line 219

def initialize
  @mappings = {}
  self.class.apply_instance_init_actions(self)
  @default_index_types = @mappings.select { |ix_type, mapping| mapping.opts[:default] }.map(&:first)
end

Instance Attribute Details

#default_index_typesObject (readonly)

—— Instance methods ——



217
218
219
# File 'lib/solrizer/field_mapper.rb', line 217

def default_index_types
  @default_index_types
end

#id_fieldObject (readonly)

—— Instance methods ——



217
218
219
# File 'lib/solrizer/field_mapper.rb', line 217

def id_field
  @id_field
end

#mappingsObject (readonly)

—— Instance methods ——



217
218
219
# File 'lib/solrizer/field_mapper.rb', line 217

def mappings
  @mappings
end

Class Method Details

.id_field(field_name) ⇒ Object



133
134
135
136
137
# File 'lib/solrizer/field_mapper.rb', line 133

def self.id_field(field_name)
  add_instance_init_action do
    @id_field = field_name
  end
end

.index_as(index_type, opts = {}, &block) ⇒ Object



139
140
141
142
143
144
145
# File 'lib/solrizer/field_mapper.rb', line 139

def self.index_as(index_type, opts = {}, &block)
  add_instance_init_action do
    mapping = (@mappings[index_type] ||= IndexTypeMapping.new)
    mapping.opts.merge! opts
    yield DataTypeMappingBuilder.new(mapping) if block_given?
  end
end

.load_mappings(config_path = nil) ⇒ Object

Loads solr mappings from yml file. Assumes that string values are solr field name suffixes.

This is meant as a simple entry point for working with solr mappings. For more powerful control over solr mappings, create your own subclasses of FieldMapper instead of using a yml file.

Parameters:

  • config_path (String) (defaults to: nil)

    This is the path to the directory where your mappings file is stored. Defaults to “Rails.root/config/solr_mappings.yml”



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/solrizer/field_mapper.rb', line 151

def self.load_mappings( config_path=nil )

  if config_path.nil? 
    if defined?(Rails.root) && !Rails.root.nil?
      config_path = File.join(Rails.root, "config", "solr_mappings.yml")
    end
    # Default to using the config file within the gem 
    if !File.exist?(config_path.to_s)
      config_path = File.join(File.dirname(__FILE__), "..", "..", "config", "solr_mappings.yml")          
    end
  end

  logger.debug("SOLRIZER: loading field name mappings from #{File.expand_path(config_path)}")
  mappings_from_file = YAML::load(File.open(config_path))
  
  self.clear_mappings
  
  # Set id_field from file if it is available
  id_field_from_file = mappings_from_file.delete("id")
  if id_field_from_file.nil?
    id_field "id"
  else
    id_field id_field_from_file
  end
  
  default_index_type = mappings_from_file.delete("default")
  mappings_from_file.each_pair do |index_type, type_settings| 
    if type_settings.kind_of?(Hash)
      index_as index_type.to_sym, :default => index_type == default_index_type do |t|
        type_settings.each_pair do |field_type, suffix|
          eval("t.#{field_type} :suffix=>\"#{suffix}\"")
        end
      end
    else
      index_as index_type.to_sym, :default => index_type == default_index_type, :suffix=>type_settings 
    end
  end      
end

Instance Method Details

#solr_name(field_name, field_type, index_type = :searchable) ⇒ Object

Given a specific field name, data type, and index type, returns the corresponding solr name.



227
228
229
230
# File 'lib/solrizer/field_mapper.rb', line 227

def solr_name(field_name, field_type, index_type = :searchable)
  name, mapping, data_type_mapping = solr_name_and_mappings(field_name, field_type, index_type)
  name
end

#solr_names_and_values(field_name, field_value, field_type, index_types) ⇒ Object

Given a field name-value pair, a data type, and an array of index types, returns a hash of mapped names and values. The values in the hash are arrays, and may contain multiple values.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/solrizer/field_mapper.rb', line 235

def solr_names_and_values(field_name, field_value, field_type, index_types)
  # Determine the set of index types, adding defaults and removing not_xyz
  
  index_types ||= []
  index_types += default_index_types
  index_types.uniq!
  index_types.dup.each do |index_type|
    if index_type.to_s =~ /^not_(.*)/
      index_types.delete index_type # not_foo
      index_types.delete $1.to_sym  # foo
    end
  end
  
  # Map names and values
  
  results = {}
  
  index_types.each do |index_type|
    # Get mapping for field
    name, mapping, data_type_mapping = solr_name_and_mappings(field_name, field_type, index_type)
    next unless name
    
    # Is there a custom converter?
    value = if data_type_mapping && data_type_mapping.converter
      converter = data_type_mapping.converter
      if converter.arity == 1
        converter.call(field_value)
      else
        converter.call(field_value, field_name)
      end
    else
      field_value
    end
    
    # Add mapped name & value, unless it's a duplicate
    values = (results[name] ||= [])
    values << value unless value.nil? || values.include?(value)
  end
  
  results
end