2019-12-05 17:51:33 +05:30
# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb
2020-03-13 15:44:24 +05:30
unless File . read ( 'README.md' ) . include? '## [2] Pretty'
2019-12-05 17:51:33 +05:30
say_status " ERROR " , " You have to run the 01-basic.rb and 02-pretty.rb templates first. " , :red
exit ( 1 )
end
begin
require 'redis'
rescue LoadError
say_status " ERROR " , " Please install the 'redis' gem before running this template " , :red
exit ( 1 )
end
begin
Redis . new . info
rescue Redis :: CannotConnectError
puts
say_status " ERROR " , " Redis not available " , :red
say_status " " , " This template uses an asynchronous indexer via Sidekiq, and requires a running Redis server. "
exit ( 1 )
end
2020-03-13 15:44:24 +05:30
append_to_file 'README.md' , <<-README
2019-12-05 17:51:33 +05:30
2020-03-13 15:44:24 +05:30
## [3] Expert
2019-12-05 17:51:33 +05:30
The ` expert ` template changes to a complex database schema with model relationships : article belongs
to a category , has many authors and comments .
* The Elasticsearch integration is refactored into the ` Searchable ` concern
* A complex mapping for the index is defined
* A custom serialization is defined in ` Article # as_indexed_json `
* The ` search ` method is amended with facets and suggestions
* A [ Sidekiq ] ( http : / /si dekiq . org ) worker for handling index updates in background is added
* A custom ` SearchController ` with associated view is added
* A Rails initializer is added to customize the Elasticsearch client configuration
* Seed script and example data from New York Times is added
README
2020-03-13 15:44:24 +05:30
git add : " README.md "
2019-12-05 17:51:33 +05:30
git commit : " -m '[03] Updated the application README' "
# ----- Add gems into Gemfile ---------------------------------------------------------------------
puts
say_status " Rubygems " , " Adding Rubygems into Gemfile... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
gem " oj "
git add : " Gemfile* "
git commit : " -m 'Added Ruby gems' "
# ----- Customize the Rails console ---------------------------------------------------------------
puts
say_status " Rails " , " Customizing `rails console`... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
gem " pry " , group : 'development'
environment nil , env : 'development' do
%q{
console do
config . console = Pry
Pry . config . history . file = Rails . root . join ( 'tmp/console_history.rb' ) . to_s
Pry . config . prompt = [ proc { | obj , nest_level , _ | " ( #{ obj } )> " } ,
proc { | obj , nest_level , _ | ' ' * obj . to_s . size + ' ' * ( nest_level + 1 ) + '| ' } ]
end
}
end
git add : " Gemfile* "
git add : " config/ "
git commit : " -m 'Added Pry as the console for development' "
# ----- Run bundle install ------------------------------------------------------------------------
run " bundle install "
# ----- Define and generate schema ----------------------------------------------------------------
puts
say_status " Models " , " Adding complex schema... \n " , :yellow
puts '-' * 80 , ''
generate :scaffold , " Category title "
generate :scaffold , " Author first_name last_name "
generate :scaffold , " Authorship article:references author:references "
generate :model , " Comment body:text user:string user_location:string stars:integer pick:boolean article:references "
generate :migration , " CreateArticlesCategories article:references category:references "
rake " db:drop "
rake " db:migrate "
insert_into_file " app/models/category.rb " , :before = > " end " do
<<-CODE
has_and_belongs_to_many :articles
CODE
end
insert_into_file " app/models/author.rb " , :before = > " end " do
<<-CODE
has_many :authorships
def full_name
[ first_name , last_name ] . join ( ' ' )
end
CODE
end
gsub_file " app/models/authorship.rb " , %r{ belongs_to :article$ } , <<-CODE
belongs_to :article , touch : true
CODE
insert_into_file " app/models/article.rb " , after : " ActiveRecord::Base " do
<<-CODE
has_and_belongs_to_many :categories , after_add : [ lambda { | a , c | Indexer . perform_async ( :update , a . class . to_s , a . id ) } ] ,
after_remove : [ lambda { | a , c | Indexer . perform_async ( :update , a . class . to_s , a . id ) } ]
has_many :authorships
has_many :authors , through : :authorships
has_many :comments
CODE
end
gsub_file " app/models/comment.rb " , %r{ belongs_to :article$ } , <<-CODE
belongs_to :article , touch : true
CODE
git add : " . "
git commit : " -m 'Generated Category, Author and Comment resources' "
# ----- Add the `abstract` column -----------------------------------------------------------------
puts
say_status " Model " , " Adding the `abstract` column to Article... \n " , :yellow
puts '-' * 80 , ''
generate :migration , " AddColumnsToArticle abstract:text url:string shares:integer "
rake " db:migrate "
git add : " db/ "
git commit : " -m 'Added additional columns to Article' "
# ----- Move the model integration into a concern -------------------------------------------------
puts
say_status " Model " , " Refactoring the model integration... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
remove_file 'app/models/article.rb'
create_file 'app/models/article.rb' , <<-CODE
class Article < ActiveRecord :: Base
include Searchable
end
CODE
2020-03-13 15:44:24 +05:30
gsub_file " test/models/article_test.rb " , %r{ assert_equal 'foo', definition \ [:query \ ] \ [:multi_match \ ] \ [:query \ ] } , " assert_equal 'foo', definition.to_hash[:query][:bool][:should][0][:multi_match][:query] "
2019-12-05 17:51:33 +05:30
# copy_file File.expand_path('../searchable.rb', __FILE__), 'app/models/concerns/searchable.rb'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb' , 'app/models/concerns/searchable.rb'
2019-12-05 17:51:33 +05:30
insert_into_file " app/models/article.rb " , after : " ActiveRecord::Base " do
<<-CODE
has_and_belongs_to_many :categories , after_add : [ lambda { | a , c | Indexer . perform_async ( :update , a . class . to_s , a . id ) } ] ,
after_remove : [ lambda { | a , c | Indexer . perform_async ( :update , a . class . to_s , a . id ) } ]
has_many :authorships
has_many :authors , through : :authorships
has_many :comments
CODE
end
git add : " app/models/ test/models "
git commit : " -m 'Refactored the Elasticsearch integration into a concern \n \n See: \n \n * http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns \n * http://joshsymonds.com/blog/2012/10/25/rails-concerns-v-searchable-with-elasticsearch/' "
# ----- Add Sidekiq indexer -----------------------------------------------------------------------
puts
2020-03-13 15:44:24 +05:30
say_status " Sidekiq " , " Adding Sidekiq worker for updating the index... \n " , :yellow
2019-12-05 17:51:33 +05:30
puts '-' * 80 , '' ; sleep 0 . 25
gem " sidekiq "
run " bundle install "
# copy_file File.expand_path('../indexer.rb', __FILE__), 'app/workers/indexer.rb'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb' , 'app/workers/indexer.rb'
2019-12-05 17:51:33 +05:30
insert_into_file " test/test_helper.rb " ,
" require 'sidekiq/testing' \n \n " ,
before : " class ActiveSupport::TestCase \n "
git add : " Gemfile* app/workers/ test/test_helper.rb "
git commit : " -m 'Added a Sidekiq indexer \n \n Run: \n \n $ bundle exec sidekiq --queue elasticsearch --verbose \n \n See http://sidekiq.org' "
# ----- Add SearchController -----------------------------------------------------------------------
puts
say_status " Controllers " , " Adding SearchController... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
create_file 'app/controllers/search_controller.rb' do
<<-CODE.gsub(/^ /, '')
class SearchController < ApplicationController
def index
options = {
category : params [ :c ] ,
author : params [ :a ] ,
published_week : params [ :w ] ,
published_day : params [ :d ] ,
sort : params [ :s ] ,
comments : params [ :comments ]
}
@articles = Article . search ( params [ :q ] , options ) . page ( params [ :page ] ) . results
end
end
CODE
end
# copy_file File.expand_path('../search_controller_test.rb', __FILE__), 'test/controllers/search_controller_test.rb'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb' , 'test/controllers/search_controller_test.rb'
2019-12-05 17:51:33 +05:30
route " get '/search', to: 'search # index', as: 'search' "
gsub_file 'config/routes.rb' , %r{ root to: 'articles # index'$ } , " root to: 'search # index' "
# copy_file File.expand_path('../index.html.erb', __FILE__), 'app/views/search/index.html.erb'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb' , 'app/views/search/index.html.erb'
2019-12-05 17:51:33 +05:30
# copy_file File.expand_path('../search.css', __FILE__), 'app/assets/stylesheets/search.css'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css' , 'app/assets/stylesheets/search.css'
2019-12-05 17:51:33 +05:30
git add : " app/controllers/ test/controllers/ config/routes.rb "
git add : " app/views/search/ app/assets/stylesheets/search.css "
git commit : " -m 'Added SearchController # index' "
2020-03-13 15:44:24 +05:30
# ----- Add SearchController -----------------------------------------------------------------------
puts
say_status " Views " , " Updating application layout... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
insert_into_file 'app/views/layouts/application.html.erb' , <<-CODE, before: '</head>'
< link href = " https://fonts.googleapis.com/css?family=Rokkitt:400,700 " rel = " stylesheet " >
CODE
git commit : " -a -m 'Updated application template' "
2019-12-05 17:51:33 +05:30
# ----- Add initializer ---------------------------------------------------------------------------
puts
say_status " Application " , " Adding Elasticsearch configuration in an initializer... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 5
create_file 'config/initializers/elasticsearch.rb' , <<-CODE
# Connect to specific Elasticsearch cluster
ELASTICSEARCH_URL = ENV [ 'ELASTICSEARCH_URL' ] || 'http://localhost:9200'
Elasticsearch :: Model . client = Elasticsearch :: Client . new host : ELASTICSEARCH_URL
# Print Curl-formatted traces in development into a file
#
if Rails . env . development?
tracer = ActiveSupport :: Logger . new ( 'log/elasticsearch.log' )
tracer . level = Logger :: DEBUG
2020-03-13 15:44:24 +05:30
Elasticsearch :: Model . client . transport . tracer = tracer
2019-12-05 17:51:33 +05:30
end
CODE
git add : " config/initializers "
git commit : " -m 'Added Rails initializer with Elasticsearch configuration' "
# ----- Add Rake tasks ----------------------------------------------------------------------------
puts
say_status " Application " , " Adding Elasticsearch Rake tasks... \n " , :yellow
puts '-' * 80 , '' ; sleep 0 . 5
create_file 'lib/tasks/elasticsearch.rake' , <<-CODE
require 'elasticsearch/rails/tasks/import'
CODE
git add : " lib/tasks "
git commit : " -m 'Added Rake tasks for Elasticsearch' "
# ----- Insert and index data ---------------------------------------------------------------------
puts
say_status " Database " , " Re-creating the database with data and importing into Elasticsearch... " , :yellow
puts '-' * 80 , '' ; sleep 0 . 25
# copy_file File.expand_path('../articles.yml.gz', __FILE__), 'db/articles.yml.gz'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz' , 'db/articles.yml.gz'
2019-12-05 17:51:33 +05:30
remove_file 'db/seeds.rb'
# copy_file File.expand_path('../seeds.rb', __FILE__), 'db/seeds.rb'
2020-03-13 15:44:24 +05:30
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb' , 'db/seeds.rb'
2019-12-05 17:51:33 +05:30
rake " db:reset "
rake " environment elasticsearch:import:model CLASS='Article' BATCH=100 FORCE=y "
git add : " db/seeds.rb db/articles.yml.gz "
git commit : " -m 'Added a seed script and source data' "
# ----- Print Git log -----------------------------------------------------------------------------
puts
say_status " Git " , " Details about the application: " , :yellow
puts '-' * 80 , ''
git tag : " expert "
git log : " --reverse --oneline HEAD...pretty "
# ----- Start the application ---------------------------------------------------------------------
unless ENV [ 'RAILS_NO_SERVER_START' ]
require 'net/http'
if ( begin ; Net :: HTTP . get ( URI ( 'http://localhost:3000' ) ) ; rescue Errno :: ECONNREFUSED ; false ; rescue Exception ; true ; end )
puts " \n "
say_status " ERROR " , " Some other application is running on port 3000! \n " , :red
puts '-' * 80
port = ask ( " Please provide free port: " , :bold )
else
port = '3000'
end
puts " " , " = " * 80
say_status " DONE " , " \e [1mStarting the application. Open http://localhost: #{ port } \e [0m " , :yellow
puts " = " * 80 , " "
run " rails server --port= #{ port } "
end