# 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