Skip to content

Instantly share code, notes, and snippets.

@elvisgiv
Last active May 12, 2016 08:36
Show Gist options
  • Save elvisgiv/586297e56b02301e15dd7fa2c95687d1 to your computer and use it in GitHub Desktop.
Save elvisgiv/586297e56b02301e15dd7fa2c95687d1 to your computer and use it in GitHub Desktop.

#Elasticsearch without DB https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence#the-activerecord-pattern

##Задача - сделать поиск записей, которые загружаются непосредственно в Elasticsearch ##Gemfile

../Gemfile
 
group :development do
  # отключает журнал трубопровода активов Rails (https://github.com/evrone/quiet_assets)
  gem 'quiet_assets'
end
# elasticsearch without DB
gem 'simple_form'
gem 'elasticsearch', git: 'git://github.com/elasticsearch/elasticsearch-ruby.git'
gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/model'
gem 'elasticsearch-persistence', git: 'git://github.com/elasticsearch/elasticsearch-rails.git', require: 'elasticsearch/persistence/model'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'

##Настройка Elasticsearch

../app/models/document.rb

class Document
  include Elasticsearch::Persistence::Model

  # for custom settings (host, log, etc) persistence elasticsearch
  include Elasticsearch::Persistence::Repository
  client Elasticsearch::Client.new host: Rails.configuration.elasticsearch_host
  
  ...some code
end
../config/enviroments/development.rb

Rails.application.configure do
 ...some code
 #
 config.elasticsearch_prefix = 'development'
 config.elasticsearch_host = '51.1.0.12'
end

##Model

../app/models/document.rb

class Document
  include Elasticsearch::Persistence::Model
  
  # for custom settings persistence elasticsearch
  include Elasticsearch::Persistence::Repository
  client Elasticsearch::Client.new host: Rails.configuration.elasticsearch_host

  index_name "#{Rails.configuration.elasticsearch_prefix}_document"

  attribute :id, Integer
  attribute :title, String
  attribute :description, String

  settings index: { number_of_shards: 1 } do
    mappings dynamic: 'false' do
      indexes :id,          :index    => :not_analyzed, :type => 'integer'
      indexes :name,        :analyzer => 'standard', :boost => 100
      indexes :description, :analyzer => 'standard', :boost => 50
    end
  end

  def self.search_me(q)

    q = "" if q.nil?

    #
    q = sanitize_string(q)

    #
    Document.search \
      query: {
          filtered: {
              query:{
                  query_string: {
                      query: q + '*',
                      fields: ['title', 'description']
                  }
              }
          }
      },
      highlight: {
          pre_tags: ['<em>'],
          post_tags: ['</em>'],
          fields: {
              name: {},
              description: {fragment_size: 80, number_of_fragments: 3}
          }
      }
  end

  def self.sanitize_string(str)
    # Escape special characters
    # http://lucene.apache.org/core/old_versioned_docs/versions/2_9_1/queryparsersyntax.html#Escaping Special Characters
    escaped_characters = Regexp.escape('\\+-&|!(){}[]^~*?:\/')
    str = str.gsub(/([#{escaped_characters}])/, '\\\\\1')

    # AND, OR and NOT are used by lucene as logical operators. We need
    # to escape them
    ['AND', 'OR', 'NOT'].each do |word|
      escaped_word = word.split('').map {|char| "\\#{char}" }.join('')
      str = str.gsub(/\s*\b(#{word.upcase})\b\s*/, " #{escaped_word} ")
    end

    # Escape odd quotes
    quote_count = str.count '"'
    str = str.gsub(/(.*)"(.*)/, '\1\"\3') if quote_count % 2 == 1

    str
  end

end

##Controller

../app/controllers/search_controller.rb

class SearchController < ApplicationController

  def index
    q = params[:q]
    @items = Document.search_me(q)
  end

end

##View

../app/views/search/index.html.haml

= stylesheet_link_tag "highlight_em", media: "all"

.container
  %header
    %h1
      Document
  %section#searchbox
    = form_tag(search_path, :method => "get", :id => "search", :class => "my_search", :style => "margin-top: 18px;") do
      = text_field_tag :q, params[:q], placeholder: "Search document", :class => "form-control"
  %br
  %br
  - if @items
    %table.table.table-striped.table-bordered.table-hover
      %tr
        %th #
        %th Title
        %th Description

      - @items.each_with_hit do |item, hit|
        %tr
          %td
            = item.id
          %td
            -if hit.try(:highlight).try(:title)
              - hit.highlight.title.each do |snippet|
                %p
                  = snippet.html_safe
            -else
              = item.title
          %td
            = item.description
            %br
            -if hit.try(:highlight).try(:description)
              %b Found:<br>
              - hit.highlight.description.each do |snippet|
                = snippet.html_safe
                %br

  - else
    %p The search hasn't returned any results...

##Stylesheets

../app/assets/styleshets/higlights.css.scss

em {
  background-color: yellow;
}

##Create record to Elasticsearch with index

  # create ONE record with index in Elasticsearch
  s_html = File.read(File.join(Rails.root, 'app/views/docs_html/installation', 'install-centos.html.erb'))
  text = s_html.strip_tags
  Document.create id: 1, title: 'Test', description: text
  • s_html = File.read(File.join(Rails.root, 'app/views/docs_html/installation', 'install-centos.html.erb')) - загружаем одну html запись из файла 'install-centos.html.erb'
  • text = s_html.strip_tags - метод, который убирает html тэги из текста см. ниже
  • Document.create id: 1, title: 'Test', description: text - команда создания записи в Elasticsearch с автоматическим индексированием https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence#persistence

##Recreate (cleaning) ES index

  # recreate (cleaning) ES index
  Document.create_index! force: true
  • Document.create_index! force: true - удаляет из Elasticsearch все записи и создает пустой индекс с требуемыми параметрами. В нашем случае {"settings":{"number_of_shards":1},"mappings":{ ... {"text":{"analyzer":"standard","type":"string"}}}}}

##Config http://apidock.com/rails/ActionView/Helpers/SanitizeHelper/strip_tags

../config/initializers/string.rb
class String

  def strip_tags
    ActionController::Base.helpers.strip_tags(self)
  end
  
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment