One of the most interesting challenges while developing an application is indexing and searching for data efficiently. Solr is a structured-document database which uses index and searches for technologies to perform its features, some of them are full-text-search, faceted search, real-time indexing, etc.
In a development team, it could be hard, and it could take a while to set up the environment for every computer, and even more if each developer has different environments (different operative systems) which hinder the process.
In order to make the process faster, and to avoid configuring all the stuff, we could use docker images.
For test purposes of this post, we will set up an application with Rails and Solr using Docker Compose. We will use Postgresql as the database engine.
So, let’s set the application up.
- docker 18.09.2.
- docker-compose 1.23.2.
Defining The Project
To define the build dependencies of the app we are going to use a file called
Dockerfile with the content below.
FROM ruby:2.4.1 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /app WORKDIR /app ADD Gemfile /app/Gemfile ADD Gemfile.lock /app/Gemfile.lock RUN bundle install ADD . /app
Now we need to create an initial
Gemfile which just loads Rails
# frozen_string_literal: true source 'https://rubygems.org' ruby '2.4.1' gem 'rails', '~> 5.1', '>= 5.1.6'
Create an empty
Gemfile.lock to build our
Finally. let’s create our
docker-compose.yml file, this is where the services are defined. To make a simple web application structure, we are going to use a
Postgres docker image and
build the web image with the Dockerfile that we previously defined, to do so we will add to the web service the property
version: '3' services: db: image: postgres volumes: - 'postgres:/var/lib/postgresql/data' web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - '.:/app' ports: - '3000:3000' depends_on: - db stdin_open: true tty: true volumes: postgres:
We made the web service dependent on the db service to wait for the start of the last one. Also, we configured the volume of the db service (postgres), so it is visible for all the services.
Building The Project
Now we have the necessary files to build the project and generate the Rails skeleton. First, we need to generate the
rails skeleton by running the command below.
docker-compose run web rails new . --force --no-deps --database=postgresql
Once we have the rails skeleton, let’s build the images.
docker-compose up --build
For now, let’s stop the services by pressing the
ctrl + c combination keys, and then we run the command below.
Connecting The Database
Replace the content of the
config/database.yml with the following.
default: &default adapter: postgresql encoding: unicode host: db username: postgres password: pool: 5 development: <<: *default database: myapp_development test: <<: *default database: myapp_test production: <<: *default database: <%= ENV['DATABASE_URL'] %>
Notice that the host is
db which is the name of the Postgres service in the docker-compose file. Now you can create the
docker-compose run web rake db:create
Creating A Simple Application
So far we have a rails infrastructure with Postgres as database engine mounted on docker. It’s time to use it in order to create an application. We will create a classic application of author-books.
Let’s create the migrations for every model by running the commands below.
bash$ docker-compose run web rails generate model Author name:string bash$ docker-compose run web rails generate model Book name:string synopsis:text
After that, we need to add a reference to the author directly in the book migration
. . . t.text :synopsis t.references :author, null: false, index: true . . .
Now we can run the migrations in the console.
docker-compose run web rails db:migrate
Establishing the associations in the models.
# frozen_string_literal: true class Book < ApplicationRecord belongs_to :author end
# frozen_string_literal: true class Author < ApplicationRecord has_many :books end
# frozen_string_literal: true class BooksController < ApplicationController def index @books = Book.all end end
With this, you can make the design of your book’s index view.
Solr Service on the docker-compose file
version: '3' services: db: image: postgres volumes: - 'postgres:/var/lib/postgresql/data' solr: image: solr:7.0.1 ports: - "8983:8983" volumes: - solr_data:/opt/solr/server/solr/mycores entrypoint: - docker-entrypoint.sh - solr-precreate - development depends_on: - db web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - '.:/app' ports: - '3000:3000' depends_on: - db - solr stdin_open: true tty: true volumes: postgres: solr_data:
The new service was added to the docker compose from the image:
Solr 7.0.1. This service was added as a dependency of the
web service and its volume in a global scope.
Add the sunspot gem
. . . gem 'sunspot_rails' . . .
In order to install the docker service and the sunspot gem we will re-build the images by running the command below.
docker-compose up --build
At this point we can access to the Solr admin console on the URL:
http://localhost:8983.On this console go to the sidebar at the bottom, select the core
development, and then select
Schema. This action will change the view of the body. Here click on the
Add Field button., and in the popup that will appear, fill the name field with “type”, select the “String” option in the field type, and make sure that only the indexed and multiValued options are checked. Finally, click on the
Add Field button at the bottom of the popup.
type field is a field which is required by the sunspot gem.
Now we need to generate the sunspot config files with the command below.
docker-compose run web rails generate sunspot_rails:install
Change the config in the
config/sunspot.yml file. In the development section, change the hostname to
solr and the port to
. . . development: solr: hostname: solr port: 8983 log_level: INFO path: /solr/development . . .
Add the index
To establish the index, set it up in the models.
. . . searchable do text :name, :synopsis text :author do author.name end end . . .
Let’s reindex for all the models.
docker-compse run web rake sunspot:solr:reindex
Change the index method in the controller in order to perform the search action.
. . . def index @search_result = Sunspot.search(Book) do fulltext params[:keyword] if params[:keyword].present? end @hits = @search_result.hits @books = @hits.map(&:instance) end . . .
Now you can search for books by their names, synopsis and the author’s name.