module Picky # = Picky Applications # # A Picky Application is where you configure the whole search engine. # # This is a step-by-step description on how to configure your Picky app. # # Start by subclassing Application: # class MyGreatSearch < Application # # Your configuration goes here. # end # The generator # $ picky generate unicorn_server project_name # will generate an example project_name/app/application.rb file for you # with some example code inside. # # == Indexes::Memory.new(name) # # Next, define where your data comes from, creating an Index. You use the Indexes::Memory.new method for that: # my_index = Indexes::Memory.new :some_index_name # You give the index a name (or identifier), and a source (see Sources), where its data comes from. Let's do that: # class MyGreatSearch < Application # # books = Indexes::Memory.new :books do # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv') # end # # end # Now we have an index books. # # That on itself won't do much good. # # Note that a Redis index is also available: Indexes::Redis.new. # # == category(identifier, options = {}) # # Picky needs us to define categories on the data. # # Categories help your user find data. # It's best if you look at an example yourself: http://floere.github.com/picky/examples.html # # Let's go ahead and define a category: # class MyGreatSearch < Application # # books = Indexes::Memory.new :books do # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv') # category :title # end # # end # Now we could already run the indexer: # $ rake index # # (You can define similarity or partial search capabilities on a category, see http://github.com/floere/picky/wiki/Categories-configuration for info) # # So now we have indexed data (the title), but nobody to ask the index anything. # # == Search.new(*indexes, options = {}) # # We need somebody who asks the index (a Query object, also see http://github.com/floere/picky/wiki/Queries-Configuration): # books_search = Search.new books # # Now we have somebody we can ask about the index. But no external interface. # # == route(/regexp1/ => search1, /regexp2/ => search2, ...) # # Let's add a URL path (a Route, see http://github.com/floere/picky/wiki/Routing-configuration) to which we can send our queries. We do that with the route method: # route %r{^/books$} => books_query # In full glory: # class MyGreatSearch < Application # # books = Indexes::Memory.new :books do # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv') # category :title # end # # route %r{^/books$} => Search.new(books) # # end # That's it! # # Now run the indexer and server: # $ rake index # $ rake start # Run your first query: # $ curl 'localhost:8080/books?query=hello server' # # Nice, right? Your first query! # # Maybe you don't find everything. We need to process the data before it goes into the index. # # == indexing(options = {}) # # That's what the indexing method is for: # indexing options # Read more about the options here: http://github.com/floere/picky/wiki/Indexing-configuration # # Same thing with the search text – we need to process that as well. # # == searching(options = {}) # # Analog to the indexing method, we use the searching method. # searching options # Read more about the options here: http://github.com/floere/picky/wiki/Searching-Configuration # # And that's all there is. It's incredibly powerful though, as you can combine, weigh, refine to the max. # # == Wiki # # Read more in the Wiki: http://github.com/floere/picky/wiki # # Have fun! # # == Full example # # Our example, fully fleshed out with indexing, querying, and weights: # class MyGreatSearch < Application # # indexing removes_characters: /[^a-zA-Z0-9\.]/, # stopwords: /\b(and|or|in|on|is|has)\b/, # splits_text_on: /\s/, # removes_characters_after_splitting: /\./, # substitutes_characters_with: CharacterSubstituters::WestEuropean.new, # normalizes_words: [ # [/(.*)hausen/, 'hn'], # [/\b(\w*)str(eet)?/, 'st'] # ] # # searching removes_characters: /[^a-zA-Z0-9\s\/\-\,\&\"\~\*\:]/, # stopwords: /\b(and|the|of|it|in|for)\b/, # splits_text_on: /[\s\/\-\,\&]+/, # removes_characters_after_splitting: /\./, # substitutes_characters_with: CharacterSubstituters::WestEuropean.new, # maximum_tokens: 4 # # books = Indexes::Memory.new :books do # source Sources::CSV.new(:title, :author, :isbn, file:'app/library.csv') # category :title, # qualifiers: [:t, :title, :titre], # partial: Partial::Substring.new(:from => 1), # similarity: Similarity::DoubleMetaphone.new(2) # category :author, # partial: Partial::Substring.new(:from => -2) # category :isbn # end # # route %r{^/books$} => Search.new(books) do # boost [:title, :author] => +3, [:author, :title] => -1 # end # # end # That's actually already a full-blown Picky App! # class Application class << self # API # # Returns a configured tokenizer that # is used for indexing by default. # def indexing options = {} Tokenizers::Index.default = Tokenizers::Index.new(options) end # Returns a configured tokenizer that # is used for querying by default. # def searching options = {} Tokenizers::Query.default = Tokenizers::Query.new(options) end # Routes. # delegate :route, :to => :rack_adapter # A Picky application implements the Rack interface. # # Delegates to its routing to handle a request. # def call env rack_adapter.call env end def rack_adapter # :nodoc: @rack_adapter || reset_rack_adapter end def reset_rack_adapter @rack_adapter = FrontendAdapters::Rack.new end # Reloads & finalizes the apps. # def reload Loader.load_user 'app' # Sinatra app_file. Loader.load_user 'app/logging' # Standard Picky logging. Loader.load_user 'app/application' # Standard Picky appfile. finalize_apps exclaim "Application #{apps.map(&:name).join(', ')} loaded." end # Finalize the subclass as soon as it # has finished loading. # attr_reader :apps # :nodoc: def initialize_apps # :nodoc: @apps ||= [] end def inherited app # :nodoc: initialize_apps apps << app end def finalize_apps # :nodoc: initialize_apps apps.each &:finalize end # Finalizes the routes. # def finalize # :nodoc: check rack_adapter.finalize end # Checks app for missing things. # # Warns if something is missing. # def check # :nodoc: warnings = [] warnings << check_external_interface warn "\n#{warnings.join(?\n)}\n\n" unless warnings.all? &:nil? end def check_external_interface "WARNING: No routes defined for application configuration in #{self.class}." if rack_adapter.empty? end def to_stats <<-APP \033[1mIndexing (default)\033[m: #{Tokenizers::Index.default.indented_to_s} \033[1mQuerying (default)\033[m: #{Tokenizers::Query.default.indented_to_s} \033[1mIndexes\033[m: #{Indexes.to_s.indented_to_s} \033[1mRoutes\033[m: #{to_routes.indented_to_s} APP end def to_routes rack_adapter.to_s end def to_s # :nodoc: self.name end end end end