debian-mirror-gitlab/elasticsearch-rails/lib/rails/templates/03-expert.rb
2020-03-13 15:44:24 +05:30

340 lines
12 KiB
Ruby

# $ rails new searchapp --skip --skip-bundle --template https://raw.github.com/elasticsearch/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/03-expert.rb
unless File.read('README.md').include? '## [2] Pretty'
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
append_to_file 'README.md', <<-README
## [3] Expert
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://sidekiq.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
git add: "README.md"
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
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]"
# copy_file File.expand_path('../searchable.rb', __FILE__), 'app/models/concerns/searchable.rb'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/searchable.rb', 'app/models/concerns/searchable.rb'
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\nSee:\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
say_status "Sidekiq", "Adding Sidekiq worker for updating the index...\n", :yellow
puts '-'*80, ''; sleep 0.25
gem "sidekiq"
run "bundle install"
# copy_file File.expand_path('../indexer.rb', __FILE__), 'app/workers/indexer.rb'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/indexer.rb', 'app/workers/indexer.rb'
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\nRun:\n\n $ bundle exec sidekiq --queue elasticsearch --verbose\n\nSee 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'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search_controller_test.rb', 'test/controllers/search_controller_test.rb'
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'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/index.html.erb', 'app/views/search/index.html.erb'
# copy_file File.expand_path('../search.css', __FILE__), 'app/assets/stylesheets/search.css'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/search.css', 'app/assets/stylesheets/search.css'
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'"
# ----- 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'"
# ----- 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
Elasticsearch::Model.client.transport.tracer = tracer
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'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/articles.yml.gz', 'db/articles.yml.gz'
remove_file 'db/seeds.rb'
# copy_file File.expand_path('../seeds.rb', __FILE__), 'db/seeds.rb'
get 'https://raw.githubusercontent.com/elastic/elasticsearch-rails/master/elasticsearch-rails/lib/rails/templates/seeds.rb', 'db/seeds.rb'
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