# Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright # ownership. Elasticsearch B.V. licenses this file to you under # the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. $LOAD_PATH.unshift File.expand_path('../../../lib/', __FILE__) require 'sinatra/base' require 'multi_json' require 'oj' require 'hashie/mash' require 'elasticsearch' require 'elasticsearch/model' require 'elasticsearch/persistence' class Note attr_reader :attributes def initialize(attributes={}) @attributes = Hashie::Mash.new(attributes) __add_date __extract_tags __truncate_text end def method_missing(method_name, *arguments, &block) attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super end def respond_to?(method_name, include_private=false) attributes.respond_to?(method_name) || super end def tags; attributes.tags || []; end def to_hash @attributes.to_hash end def __extract_tags tags = attributes['text'].scan(/(\[\w+\])/).flatten if attributes['text'] unless tags.nil? || tags.empty? attributes.update 'tags' => tags.map { |t| t.tr('[]', '') } attributes['text'].gsub!(/(\[\w+\])/, '').strip! end end def __add_date attributes['created_at'] ||= Time.now.utc.iso8601 end def __truncate_text attributes['text'] = attributes['text'][0...80] + ' (...)' if attributes['text'] && attributes['text'].size > 80 end end class NoteRepository include Elasticsearch::Persistence::Repository include Elasticsearch::Persistence::Repository::DSL client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true index_name :notes document_type :note mapping do indexes :text, analyzer: 'snowball' indexes :tags, type: 'keyword' indexes :created_at, type: 'date' end def deserialize(document) Note.new document['_source'].merge('id' => document['_id']) end end unless defined?(NoteRepository) class Application < Sinatra::Base enable :logging enable :inline_templates enable :method_override configure :development do enable :dump_errors disable :show_exceptions require 'sinatra/reloader' register Sinatra::Reloader end set :repository, NoteRepository.new set :per_page, 25 get '/' do @page = [ params[:p].to_i, 1 ].max @notes = settings.repository.search \ query: ->(q, t) do query = if q && !q.empty? { match: { text: q } } else { match_all: {} } end filter = if t && !t.empty? { term: { tags: t } } end if filter { bool: { must: [ query ], filter: filter } } else query end end.(params[:q], params[:t]), sort: [{created_at: {order: 'desc'}}], size: settings.per_page, from: settings.per_page * (@page-1), aggregations: { tags: { terms: { field: 'tags' } } }, highlight: { fields: { text: { fragment_size: 0, pre_tags: [''],post_tags: [''] } } } erb :index end post '/' do unless params[:text].empty? @note = Note.new params settings.repository.save(@note, refresh: true) end redirect back end delete '/:id' do |id| settings.repository.delete(id, refresh: true) redirect back end end Application.run! if $0 == __FILE__ __END__ @@ layout Notes <%= yield %> @@ index

Notes

All notes <%= @notes.size %>

Add a note

<% if @notes.empty? %>

No notes found.

<% end %> <% @notes.each_with_hit do |note, hit| %>

<%= hit.highlight && hit.highlight.size > 0 ? hit.highlight.text.first : note.text %> <% note.tags.each do |tag| %> <%= tag %><% end %> <%= Time.parse(note.created_at).strftime('%d/%m/%Y %H:%M') %>

<% end %> <% if @notes.size > 0 && @page.next <= @notes.total / settings.per_page %>

→ Load next

<% end %>