From f8321c94fbcfd0cf7556d3e85781a712c3e88a8d Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Thu, 22 Sep 2016 19:23:49 +0300 Subject: [PATCH 01/20] Install Sphinx --- Gemfile | 3 +++ Gemfile.lock | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/Gemfile b/Gemfile index 69d3754..3e7d13b 100644 --- a/Gemfile +++ b/Gemfile @@ -35,6 +35,9 @@ gem 'sidekiq' gem 'sinatra', require: false gem 'whenever' +gem 'mysql2' # Don't worry, it's for Sphinx only! +gem 'thinking-sphinx' + # gem 'unicorn' gem 'thin' diff --git a/Gemfile.lock b/Gemfile.lock index 888a8c9..14c6a85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -164,9 +164,12 @@ GEM hashie (3.4.6) http_parser.rb (0.6.0) i18n (0.7.0) + innertube (1.1.0) jbuilder (2.6.0) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) + joiner (0.3.4) + activerecord (>= 4.1.0) jquery-rails (4.2.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -196,6 +199,7 @@ GEM rack-contrib (~> 1.1) railties (>= 3.0.0, < 5.1.0) method_source (0.8.2) + middleware (0.1.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) @@ -205,6 +209,7 @@ GEM multi_json (1.12.1) multi_xml (0.5.5) multipart-post (2.0.0) + mysql2 (0.4.4) nenv (0.3.0) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) @@ -309,6 +314,7 @@ GEM require_all (1.3.3) responders (2.3.0) railties (>= 4.2.0, < 5.1) + riddle (1.5.12) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) @@ -391,6 +397,13 @@ GEM daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) + thinking-sphinx (3.2.0) + activerecord (>= 3.1.0) + builder (>= 2.1.2) + innertube (>= 1.0.2) + joiner (>= 0.2.0) + middleware (>= 0.1.0) + riddle (>= 1.5.11) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.5) @@ -446,6 +459,7 @@ DEPENDENCIES launchy letter_opener meta_request + mysql2 oj oj_mimic_json omniauth @@ -477,6 +491,7 @@ DEPENDENCIES sprockets (= 3.6.3) test_after_commit thin + thinking-sphinx turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) From 3b5c3d8b773e09e3d1837ac1cc4c490a747f67e2 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Thu, 22 Sep 2016 19:24:19 +0300 Subject: [PATCH 02/20] Add Sphinx generated config and db to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index f8e2b67..292859e 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ pickle-email-*.html # Ignore Byebug command history file. .byebug_history + +# Ignore Sphinx generadet config and database +/config/*.sphinx.conf +/db/sphinx From 646905fc3897d81223d7f571d10de6f6013e7b41 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Thu, 22 Sep 2016 19:49:10 +0300 Subject: [PATCH 03/20] Add Sphinx indices and some configuration --- app/indices/answers_index.rb | 3 +++ app/indices/comments_index.rb | 3 +++ app/indices/questions_index.rb | 5 +++++ app/indices/users_index.rb | 3 +++ config/thinking_sphinx.yml | 2 ++ spec/support/sphinx.rb | 28 ++++++++++++++++++++++++++++ 6 files changed, 44 insertions(+) create mode 100644 app/indices/answers_index.rb create mode 100644 app/indices/comments_index.rb create mode 100644 app/indices/questions_index.rb create mode 100644 app/indices/users_index.rb create mode 100644 config/thinking_sphinx.yml create mode 100644 spec/support/sphinx.rb diff --git a/app/indices/answers_index.rb b/app/indices/answers_index.rb new file mode 100644 index 0000000..97bde9e --- /dev/null +++ b/app/indices/answers_index.rb @@ -0,0 +1,3 @@ +ThinkingSphinx::Index.define :answer, with: :active_record do + indexes body +end diff --git a/app/indices/comments_index.rb b/app/indices/comments_index.rb new file mode 100644 index 0000000..f914a81 --- /dev/null +++ b/app/indices/comments_index.rb @@ -0,0 +1,3 @@ +ThinkingSphinx::Index.define :comment, with: :active_record do + indexes body +end diff --git a/app/indices/questions_index.rb b/app/indices/questions_index.rb new file mode 100644 index 0000000..cb25296 --- /dev/null +++ b/app/indices/questions_index.rb @@ -0,0 +1,5 @@ +ThinkingSphinx::Index.define :question, with: :active_record do + indexes topic, sortable: true + indexes body + indexes user.email +end diff --git a/app/indices/users_index.rb b/app/indices/users_index.rb new file mode 100644 index 0000000..97bde9e --- /dev/null +++ b/app/indices/users_index.rb @@ -0,0 +1,3 @@ +ThinkingSphinx::Index.define :answer, with: :active_record do + indexes body +end diff --git a/config/thinking_sphinx.yml b/config/thinking_sphinx.yml new file mode 100644 index 0000000..339e1b5 --- /dev/null +++ b/config/thinking_sphinx.yml @@ -0,0 +1,2 @@ +test: + mysql41: 9307 diff --git a/spec/support/sphinx.rb b/spec/support/sphinx.rb new file mode 100644 index 0000000..1942e31 --- /dev/null +++ b/spec/support/sphinx.rb @@ -0,0 +1,28 @@ +module SphinxHelpers + def index + ThinkingSphinx::Test.index + # Wait for Sphinx to finish loading in the new index files. + sleep 0.25 until index_finished? + end + + def index_finished? + Dir[Rails.root.join(ThinkingSphinx::Test.config.indices_location, '*.{new,tmp}*')].empty? + end +end + +RSpec.configure do |config| + config.include SphinxHelpers, type: :feature + + config.before(:suite) do + # Ensure sphinx directories exist for the test environment + ThinkingSphinx::Test.init + # Configure and start Sphinx, and automatically + # stop Sphinx at the end of the test suite. + ThinkingSphinx::Test.start_with_autostop + end + + config.before(:each) do + # Index data when running an acceptance spec. + index if example.metadata[:js] + end +end From 9321893771656929cdc143bdce5d31820bb6b6ae Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Thu, 22 Sep 2016 20:11:04 +0300 Subject: [PATCH 04/20] Draft search feature spec --- spec/features/search_spec.rb | 113 +++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 spec/features/search_spec.rb diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb new file mode 100644 index 0000000..2629a49 --- /dev/null +++ b/spec/features/search_spec.rb @@ -0,0 +1,113 @@ +require 'features_helper' + +feature 'Search Questions, Answers, Comments and Users', %q( + To get information I need + As a Guest + I want to search information in Questions, Answers, Comments and User emails +) do + given!(:question) { create(:question) } + given!(:answer) { create(:answer) } + given!(:comment) { create(:comment) } + given!(:user) { create(:user) } + + background { visit questions_path } + + describe 'in global scope' do + scenario 'search Questions' do + fill_in 'q', with: question.topic + click_on 'Search' + expect(page).to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Answers' do + fill_in 'q', with: answer.body + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Comments' do + fill_in 'q', with: comment.body + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Users' do + fill_in 'q', with: user.email + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).to have_content user.email + end + + scenario 'search unsearchable' do + fill_in 'q', with: 'thetextthatfactorycannotgenerate' + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + end + + describe 'in typed scope' do + scenario 'search Questions' do + fill_in 'q', with: question.topic + select 'q', from: 'type' + click_on 'Search' + expect(page).to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Answers' do + fill_in 'q', with: answer.body + select 'a', from: 'type' + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Comments' do + fill_in 'q', with: comment.body + select 't', from: 'type' + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).to have_content comment.body + expect(page).not_to have_content user.email + end + + scenario 'search Users' do + fill_in 'q', with: user.email + select 'u', from: 'type' + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).to have_content user.email + end + + scenario 'search with invalid type' do + fill_in 'q', with: 'thetextthatfactorycannotgenerate' + select 'x', from: 'type' + click_on 'Search' + expect(page).not_to have_content question.topic + expect(page).not_to have_content answer.body + expect(page).not_to have_content comment.body + expect(page).not_to have_content user.email + end + end +end From f3a784c54ca9fe7cd601a5074965e15a7dca5f99 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Thu, 22 Sep 2016 21:58:43 +0300 Subject: [PATCH 05/20] Draft SearchController w/view and form in navbar --- app/assets/stylesheets/search.scss | 7 + app/controllers/search_controller.rb | 18 ++ app/views/search/no_results.slim | 4 + app/views/search/search.slim | 4 + app/views/shared/_navbar.slim | 13 +- config/routes.rb | 2 + spec/controllers/search_controller_spec.rb | 28 ++++ spec/features/search_spec.rb | 183 +++++++++++---------- spec/support/sphinx.rb | 8 +- 9 files changed, 171 insertions(+), 96 deletions(-) create mode 100644 app/assets/stylesheets/search.scss create mode 100644 app/controllers/search_controller.rb create mode 100644 app/views/search/no_results.slim create mode 100644 app/views/search/search.slim create mode 100644 spec/controllers/search_controller_spec.rb diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss new file mode 100644 index 0000000..0ba8a5a --- /dev/null +++ b/app/assets/stylesheets/search.scss @@ -0,0 +1,7 @@ +#global-search { + select { + border-radius:0; + border-left:none; + -webkit-appearance:none; + } +} diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb new file mode 100644 index 0000000..a1b922a --- /dev/null +++ b/app/controllers/search_controller.rb @@ -0,0 +1,18 @@ +class SearchController < ApplicationController + before_filter :get_search_params + + def search + end + + private + + def get_search_params + @search_query = params[:q] + @search_type = params[:t].to_s + puts "st: |#{@search_type.blank?}|" + unless @search_type.blank? || %w(q a c u).include?(@search_type) + @search_type = nil + render :no_results, status: :unprocessable_entity + end + end +end diff --git a/app/views/search/no_results.slim b/app/views/search/no_results.slim new file mode 100644 index 0000000..f29ad41 --- /dev/null +++ b/app/views/search/no_results.slim @@ -0,0 +1,4 @@ +h2 Search returns no results + +.well style="margin-top:40px" + p We sorry but try to search with another query! diff --git a/app/views/search/search.slim b/app/views/search/search.slim new file mode 100644 index 0000000..daaaf34 --- /dev/null +++ b/app/views/search/search.slim @@ -0,0 +1,4 @@ +h2 Search Results + +.well + p results should be here! diff --git a/app/views/shared/_navbar.slim b/app/views/shared/_navbar.slim index c426909..85a498b 100644 --- a/app/views/shared/_navbar.slim +++ b/app/views/shared/_navbar.slim @@ -6,14 +6,14 @@ nav.navbar.navbar-default role="navigation" span.icon-bar = link_to root_path, class: 'navbar-brand' = image_tag 'logo.png', size: '24x24', alt: 'Deep Recursion', class: 'pull-left' - |   Deep Recursion + |   D/R #navbar-collapse.collapse.navbar-collapse ul.nav.navbar-nav - = navbar_item 'Questions List', questions_path + = navbar_item 'Questions', questions_path - if policy(Question).new? = navbar_item 'Ask a Question', new_question_path - if current_user&.admin? - = navbar_item 'Applications', oauth_applications_path + = navbar_item 'Apps', oauth_applications_path ul.nav.navbar-nav.navbar-right li - if user_signed_in? @@ -27,3 +27,10 @@ nav.navbar.navbar-default role="navigation" = navbar_item new_user_session_path => glyph 'log-in' | Sign In + = form_tag search_path, method: :get, class: 'navbar-form navbar-right' + .form-group + .input-group#global-search + = search_field_tag :q, @search_query, class: 'form-control', size: 30, placeholder: 'Search for...' + span.input-group-btn + = select_tag :t, options_for_select({questions: :q, answers: :a, comments: :c, users: :u}, @search_type), prompt: 'anything ▾', class: 'form-control' + button.btn.btn-default type="submit" Go! diff --git a/config/routes.rb b/config/routes.rb index d114f96..4f69500 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,8 @@ devise_for :users, controllers: {omniauth_callbacks: :omniauth_callbacks} match 'account/confirm_email', via: [:get, :patch] + get :search, to: 'search#search' + concern :rateable do member do post :rate_inc diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb new file mode 100644 index 0000000..1c2f5e6 --- /dev/null +++ b/spec/controllers/search_controller_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +describe SearchController do + describe 'GET #search' do + context 'when query is valid' do + + it 'should set @found' + + context 'performs search using Sphinx' + + subject { response } + it { is_expected.to have_http_status :success } + it { is_expected.to render_template :search } + end + + context 'when query is invalid' do + + it 'shouldn\'t set @found' + + it 'shouldn\'t performs search using Sphinx' + + subject { response } + it { is_expected.to have_http_status :unprocessable_entity } + it { is_expected.to render_template :search } + end + + end +end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 2629a49..d646db0 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -12,102 +12,107 @@ background { visit questions_path } - describe 'in global scope' do + describe 'In global scope' do scenario 'search Questions' do - fill_in 'q', with: question.topic - click_on 'Search' - expect(page).to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end + within '#global-search' do + fill_in 'q', with: question.topic + click_button 'Go!' + end - scenario 'search Answers' do - fill_in 'q', with: answer.body - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end + expect(current_path).to eq '/search' - scenario 'search Comments' do - fill_in 'q', with: comment.body - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).to have_content comment.body - expect(page).not_to have_content user.email - end - - scenario 'search Users' do - fill_in 'q', with: user.email - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).to have_content user.email - end - - scenario 'search unsearchable' do - fill_in 'q', with: 'thetextthatfactorycannotgenerate' - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end - end - - describe 'in typed scope' do - scenario 'search Questions' do - fill_in 'q', with: question.topic - select 'q', from: 'type' - click_on 'Search' expect(page).to have_content question.topic expect(page).not_to have_content answer.body expect(page).not_to have_content comment.body expect(page).not_to have_content user.email end - - scenario 'search Answers' do - fill_in 'q', with: answer.body - select 'a', from: 'type' - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end - - scenario 'search Comments' do - fill_in 'q', with: comment.body - select 't', from: 'type' - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).to have_content comment.body - expect(page).not_to have_content user.email - end - - scenario 'search Users' do - fill_in 'q', with: user.email - select 'u', from: 'type' - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).to have_content user.email - end - - scenario 'search with invalid type' do - fill_in 'q', with: 'thetextthatfactorycannotgenerate' - select 'x', from: 'type' - click_on 'Search' - expect(page).not_to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end end + + # scenario 'search Answers' do + # fill_in 'q', with: answer.body + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).not_to have_content user.email + # end + # + # scenario 'search Comments' do + # fill_in 'q', with: comment.body + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).to have_content comment.body + # expect(page).not_to have_content user.email + # end + # + # scenario 'search Users' do + # fill_in 'q', with: user.email + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).to have_content user.email + # end + # + # scenario 'search unsearchable' do + # fill_in 'q', with: 'thetextthatfactorycannotgenerate' + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).not_to have_content user.email + # end + # end + # + # describe 'In typed scope' do + # scenario 'search Questions' do + # fill_in 'q', with: question.topic + # select 'q', from: 'type' + # click_on 'Search' + # expect(page).to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).not_to have_content user.email + # end + # + # scenario 'search Answers' do + # fill_in 'q', with: answer.body + # select 'a', from: 'type' + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).not_to have_content user.email + # end + # + # scenario 'search Comments' do + # fill_in 'q', with: comment.body + # select 't', from: 'type' + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).to have_content comment.body + # expect(page).not_to have_content user.email + # end + # + # scenario 'search Users' do + # fill_in 'q', with: user.email + # select 'u', from: 'type' + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).to have_content user.email + # end + # + # scenario 'search with invalid type' do + # fill_in 'q', with: 'thetextthatfactorycannotgenerate' + # select 'x', from: 'type' + # click_on 'Search' + # expect(page).not_to have_content question.topic + # expect(page).not_to have_content answer.body + # expect(page).not_to have_content comment.body + # expect(page).not_to have_content user.email + # end end diff --git a/spec/support/sphinx.rb b/spec/support/sphinx.rb index 1942e31..0eef5c8 100644 --- a/spec/support/sphinx.rb +++ b/spec/support/sphinx.rb @@ -21,8 +21,8 @@ def index_finished? ThinkingSphinx::Test.start_with_autostop end - config.before(:each) do - # Index data when running an acceptance spec. - index if example.metadata[:js] - end + # config.before(:each) do + # # Index data when running an acceptance spec. + # index if example.metadata[:js] + # end end From 7f8d0c15605f433d1d1f3f53e0b0a84f69c6f1ec Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Fri, 23 Sep 2016 01:07:23 +0300 Subject: [PATCH 06/20] Fix Sphinx indices --- app/indices/questions_index.rb | 1 - app/indices/users_index.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/indices/questions_index.rb b/app/indices/questions_index.rb index cb25296..ab0d439 100644 --- a/app/indices/questions_index.rb +++ b/app/indices/questions_index.rb @@ -1,5 +1,4 @@ ThinkingSphinx::Index.define :question, with: :active_record do indexes topic, sortable: true indexes body - indexes user.email end diff --git a/app/indices/users_index.rb b/app/indices/users_index.rb index 97bde9e..8a25f02 100644 --- a/app/indices/users_index.rb +++ b/app/indices/users_index.rb @@ -1,3 +1,3 @@ -ThinkingSphinx::Index.define :answer, with: :active_record do - indexes body +ThinkingSphinx::Index.define :user, with: :active_record do + indexes user.email end From 9de295f534daa0c1133abab15a994b0e13c9698f Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Fri, 23 Sep 2016 02:39:38 +0300 Subject: [PATCH 07/20] Search implementation w/o specs and indices rebuild hooks --- Gemfile.lock | 2 +- app/controllers/search_controller.rb | 13 +++++++++++++ app/indices/users_index.rb | 2 +- app/views/comments/_comment.slim | 2 +- app/views/search/_answer.slim | 7 +++++++ app/views/search/_comment.slim | 13 +++++++++++++ app/views/search/_question.slim | 4 ++++ app/views/search/_user.slim | 4 ++++ app/views/search/search.slim | 13 +++++++++++-- 9 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 app/views/search/_answer.slim create mode 100644 app/views/search/_comment.slim create mode 100644 app/views/search/_question.slim create mode 100644 app/views/search/_user.slim diff --git a/Gemfile.lock b/Gemfile.lock index 14c6a85..3c3fb5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -243,7 +243,7 @@ GEM orm_adapter (0.5.0) parser (2.3.1.4) ast (~> 2.2) - pg (0.18.4) + pg (0.19.0) pkg-config (1.1.7) powerpack (0.1.1) private_pub (1.0.3) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index a1b922a..da0403a 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -2,10 +2,23 @@ class SearchController < ApplicationController before_filter :get_search_params def search + @found = search_class.search(@search_query) + render :no_results if @found.empty? end private + def search_class + puts "ST: #{@search_type}" + case @search_type + when '' then ThinkingSphinx + when 'q' then Question + when 'a' then Answer + when 'c' then Commient + when 'u' then User + end + end + def get_search_params @search_query = params[:q] @search_type = params[:t].to_s diff --git a/app/indices/users_index.rb b/app/indices/users_index.rb index 8a25f02..d03879e 100644 --- a/app/indices/users_index.rb +++ b/app/indices/users_index.rb @@ -1,3 +1,3 @@ ThinkingSphinx::Index.define :user, with: :active_record do - indexes user.email + indexes email end diff --git a/app/views/comments/_comment.slim b/app/views/comments/_comment.slim index 39dfa2c..e41e630 100644 --- a/app/views/comments/_comment.slim +++ b/app/views/comments/_comment.slim @@ -1,4 +1,4 @@ -p +p id="comment-#{comment.id}" = comment.body span.little style="color:gray" ' diff --git a/app/views/search/_answer.slim b/app/views/search/_answer.slim new file mode 100644 index 0000000..633ffc6 --- /dev/null +++ b/app/views/search/_answer.slim @@ -0,0 +1,7 @@ +p + ' Answer to the Question + i + = link_to "\"#{entry.question.topic}\"", entry.question + | : +p.lead + = link_to entry.body, question_path(entry.question, anchor: "answer-#{entry.id}") \ No newline at end of file diff --git a/app/views/search/_comment.slim b/app/views/search/_comment.slim new file mode 100644 index 0000000..49100e6 --- /dev/null +++ b/app/views/search/_comment.slim @@ -0,0 +1,13 @@ +- commentable = entry.commentable +- question = commentable.kind_of?(Question) ? commentable : commentable.question +p + ' Comment to the + => commentable.class.name + i + - if commentable.kind_of?(Question) + = link_to "\"#{commentable.topic}\"", commentable + - else + = link_to "\"#{commentable.body}\"", question_path(question, anchor: "answer-#{commentable.id}") + | : +p.lead + = link_to entry.body, question_path(question, anchor: "comment-#{entry.id}") diff --git a/app/views/search/_question.slim b/app/views/search/_question.slim new file mode 100644 index 0000000..ada8976 --- /dev/null +++ b/app/views/search/_question.slim @@ -0,0 +1,4 @@ +p + ' Question: +p.lead + = link_to entry.topic, entry \ No newline at end of file diff --git a/app/views/search/_user.slim b/app/views/search/_user.slim new file mode 100644 index 0000000..05dde72 --- /dev/null +++ b/app/views/search/_user.slim @@ -0,0 +1,4 @@ +p + ' User with email: +p.lead + = link_to entry.email, "mailto:#{entry.email}" \ No newline at end of file diff --git a/app/views/search/search.slim b/app/views/search/search.slim index daaaf34..7e8b924 100644 --- a/app/views/search/search.slim +++ b/app/views/search/search.slim @@ -1,4 +1,13 @@ h2 Search Results -.well - p results should be here! +h4 #{pluralize(@found.count, 'entry')} found: + +- @found.each_with_index do |entry, cnt| + .well + .row + .col-xs-1 + p.lead + = cnt + 1 + | . + .col-xs-11 + = render entry.class.name.downcase, entry: entry From 46b2bf231fa7d1c49dda9773dd95632a496b7d67 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Fri, 23 Sep 2016 23:24:06 +0300 Subject: [PATCH 08/20] WIP CommentsController ans specs --- app/controllers/search_controller.rb | 16 +---- app/models/search_service.rb | 28 +++++++++ spec/controllers/search_controller_spec.rb | 69 +++++++++++++++++----- 3 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 app/models/search_service.rb diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index da0403a..c414293 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -2,30 +2,18 @@ class SearchController < ApplicationController before_filter :get_search_params def search - @found = search_class.search(@search_query) + @found = SearchService.search(type: @search_type, query: @search_query) render :no_results if @found.empty? end private - def search_class - puts "ST: #{@search_type}" - case @search_type - when '' then ThinkingSphinx - when 'q' then Question - when 'a' then Answer - when 'c' then Commient - when 'u' then User - end - end - def get_search_params @search_query = params[:q] @search_type = params[:t].to_s - puts "st: |#{@search_type.blank?}|" unless @search_type.blank? || %w(q a c u).include?(@search_type) @search_type = nil - render :no_results, status: :unprocessable_entity + render nothing: true, status: :not_implemented end end end diff --git a/app/models/search_service.rb b/app/models/search_service.rb new file mode 100644 index 0000000..2823c43 --- /dev/null +++ b/app/models/search_service.rb @@ -0,0 +1,28 @@ +class SearchService + class << self + def search(params) + query = ThinkingSphinx::Query.escape(params[:query] || '') + type = params[:type] || '' + klass(type)&.search(query) + end + + private + + def klass(type) + case type + when '' then + ThinkingSphinx + when 'q' then + Question + when 'a' then + Answer + when 'c' then + Comment + when 'u' then + User + else + nil + end + end + end +end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 1c2f5e6..d2706e8 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,28 +1,65 @@ require 'rails_helper' -describe SearchController do - describe 'GET #search' do - context 'when query is valid' do +shared_examples :search_some do |object| + # let!(:object) { create(:question) } - it 'should set @found' + it 'should call SearchService with corrent params' do + expect(SearchService).to receive(:klass).with('q').and_return(Question) + expect(SearchService).to receive(:search).with(type: 'q', query: object.topic).and_call_original + get :search, q: object.topic, t: 'q' + end - context 'performs search using Sphinx' + context 'calling search on empty database' do + before { get :search, q: object.topic, t: 'q' } + subject { response } - subject { response } - it { is_expected.to have_http_status :success } - it { is_expected.to render_template :search } + it 'should set @found to empty Array' do + get :search, q: object.topic, t: 'q' + expect(assigns(:found)).to eq [] end - context 'when query is invalid' do - - it 'shouldn\'t set @found' + it { is_expected.to have_http_status :success } + it { is_expected.to render_template :no_results } + end +end - it 'shouldn\'t performs search using Sphinx' +describe SearchController do + describe 'GET #search' do - subject { response } - it { is_expected.to have_http_status :unprocessable_entity } - it { is_expected.to render_template :search } - end + include_examples :search_some, let!(:question) { create(:question) } + # let!(:object) { create(:question) } + # + # # context 'when query is invalid' do + # # subject { get :search, t: 'ZZZ' } + # # + # # it 'shouldn\'t set @found' + # # + # # it 'shouldn\'t performs search using Sphinx' + # # + # # subject { response } + # # it { is_expected.to have_http_status :success } + # # it { is_expected.to render_template :no_results } + # # end + # + # it 'should call SearchService with corrent params' do + # #question = double(:question, title: Faker::Lorem.sentence) + # expect(SearchService).to receive(:klass).with('q').and_return(Question) + # expect(SearchService).to receive(:search).with(type: 'q', query: object.topic).and_call_original + # get :search, q: object.topic, t: 'q' + # end + # + # context 'calling search on empty database' do + # before { get :search, q: object.topic, t: 'q' } + # subject { response } + # + # it 'should set @found to empty Array' do + # get :search, q: object.topic, t: 'q' + # expect(assigns(:found)).to eq [] + # end + # + # it { is_expected.to have_http_status :success } + # it { is_expected.to render_template :no_reqults } + # end end end From 7c54d83af14ce70bcd48b53f880bc6f6dce81ada Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sat, 24 Sep 2016 11:23:07 +0300 Subject: [PATCH 09/20] Set up Sphinx Delta Indexes --- app/indices/answers_index.rb | 2 +- app/indices/comments_index.rb | 2 +- app/indices/questions_index.rb | 2 +- app/indices/users_index.rb | 2 +- config/schedule.rb | 4 +++ .../20160924001243_create_sphinx_deltas.rb | 8 +++++ db/schema.rb | 32 +++++++++++-------- 7 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 db/migrate/20160924001243_create_sphinx_deltas.rb diff --git a/app/indices/answers_index.rb b/app/indices/answers_index.rb index 97bde9e..1e55cdd 100644 --- a/app/indices/answers_index.rb +++ b/app/indices/answers_index.rb @@ -1,3 +1,3 @@ -ThinkingSphinx::Index.define :answer, with: :active_record do +ThinkingSphinx::Index.define :answer, with: :active_record, delta: true do indexes body end diff --git a/app/indices/comments_index.rb b/app/indices/comments_index.rb index f914a81..de4668f 100644 --- a/app/indices/comments_index.rb +++ b/app/indices/comments_index.rb @@ -1,3 +1,3 @@ -ThinkingSphinx::Index.define :comment, with: :active_record do +ThinkingSphinx::Index.define :comment, with: :active_record, delta: true do indexes body end diff --git a/app/indices/questions_index.rb b/app/indices/questions_index.rb index ab0d439..3b156d1 100644 --- a/app/indices/questions_index.rb +++ b/app/indices/questions_index.rb @@ -1,4 +1,4 @@ -ThinkingSphinx::Index.define :question, with: :active_record do +ThinkingSphinx::Index.define :question, with: :active_record, delta: true do indexes topic, sortable: true indexes body end diff --git a/app/indices/users_index.rb b/app/indices/users_index.rb index d03879e..8dccf1b 100644 --- a/app/indices/users_index.rb +++ b/app/indices/users_index.rb @@ -1,3 +1,3 @@ -ThinkingSphinx::Index.define :user, with: :active_record do +ThinkingSphinx::Index.define :user, with: :active_record, delta: true do indexes email end diff --git a/config/schedule.rb b/config/schedule.rb index b8a02b2..2825520 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -1,3 +1,7 @@ every 1.day, at: '9:00' do runner 'MailDigestJob.perform_later' end + +every 1.hour, at: 42 do + rake 'ts:rebuild' +end diff --git a/db/migrate/20160924001243_create_sphinx_deltas.rb b/db/migrate/20160924001243_create_sphinx_deltas.rb new file mode 100644 index 0000000..b9d924b --- /dev/null +++ b/db/migrate/20160924001243_create_sphinx_deltas.rb @@ -0,0 +1,8 @@ +class CreateSphinxDeltas < ActiveRecord::Migration + def up + add_column :questions, :delta, :boolean, default: true, null: false + add_column :answers, :delta, :boolean, default: true, null: false + add_column :comments, :delta, :boolean, default: true, null: false + add_column :users, :delta, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index a76f79f..5f0fc3c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160921133756) do +ActiveRecord::Schema.define(version: 20160924001243) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -24,6 +24,7 @@ t.datetime "updated_at", null: false t.integer "user_id" t.boolean "starred", default: false, null: false + t.boolean "delta", default: true, null: false end add_index "answers", ["question_id"], name: "index_answers_on_question_id", using: :btree @@ -43,9 +44,10 @@ t.integer "user_id" t.integer "commentable_id" t.string "commentable_type" - t.text "body", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.text "body", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "delta", default: true, null: false end create_table "identities", force: :cascade do |t| @@ -101,12 +103,13 @@ add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree create_table "questions", force: :cascade do |t| - t.string "topic", limit: 200, null: false - t.text "body", null: false - t.integer "rating", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "topic", limit: 200, null: false + t.text "body", null: false + t.integer "rating", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "user_id" + t.boolean "delta", default: true, null: false end add_index "questions", ["user_id"], name: "index_questions_on_user_id", using: :btree @@ -133,14 +136,14 @@ add_index "subscriptions", ["user_id", "question_id"], name: "index_subscriptions_on_user_id_and_question_id", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.inet "current_sign_in_ip" @@ -150,6 +153,7 @@ t.datetime "confirmation_sent_at" t.string "unconfirmed_email" t.boolean "admin" + t.boolean "delta", default: true, null: false end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree From 98e8e0badf677786524b8d6d0a4b39832e2cf540 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sat, 24 Sep 2016 11:27:17 +0300 Subject: [PATCH 10/20] SearchController with spec ans shared examples --- spec/controllers/search_controller_spec.rb | 66 ++----------------- .../shared_examples/controllers/search.rb | 47 +++++++++++++ 2 files changed, 53 insertions(+), 60 deletions(-) create mode 100644 spec/support/shared_examples/controllers/search.rb diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index d2706e8..e83c82a 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,65 +1,11 @@ require 'rails_helper' -shared_examples :search_some do |object| - # let!(:object) { create(:question) } - - it 'should call SearchService with corrent params' do - expect(SearchService).to receive(:klass).with('q').and_return(Question) - expect(SearchService).to receive(:search).with(type: 'q', query: object.topic).and_call_original - get :search, q: object.topic, t: 'q' - end - - context 'calling search on empty database' do - before { get :search, q: object.topic, t: 'q' } - subject { response } - - it 'should set @found to empty Array' do - get :search, q: object.topic, t: 'q' - expect(assigns(:found)).to eq [] - end - - it { is_expected.to have_http_status :success } - it { is_expected.to render_template :no_results } - end -end - describe SearchController do - describe 'GET #search' do - - include_examples :search_some, let!(:question) { create(:question) } - - # let!(:object) { create(:question) } - # - # # context 'when query is invalid' do - # # subject { get :search, t: 'ZZZ' } - # # - # # it 'shouldn\'t set @found' - # # - # # it 'shouldn\'t performs search using Sphinx' - # # - # # subject { response } - # # it { is_expected.to have_http_status :success } - # # it { is_expected.to render_template :no_results } - # # end - # - # it 'should call SearchService with corrent params' do - # #question = double(:question, title: Faker::Lorem.sentence) - # expect(SearchService).to receive(:klass).with('q').and_return(Question) - # expect(SearchService).to receive(:search).with(type: 'q', query: object.topic).and_call_original - # get :search, q: object.topic, t: 'q' - # end - # - # context 'calling search on empty database' do - # before { get :search, q: object.topic, t: 'q' } - # subject { response } - # - # it 'should set @found to empty Array' do - # get :search, q: object.topic, t: 'q' - # expect(assigns(:found)).to eq [] - # end - # - # it { is_expected.to have_http_status :success } - # it { is_expected.to render_template :no_reqults } - # end + describe 'GET #search' do + include_examples :search_with_sphinx, :question, :topic + include_examples :search_with_sphinx, :question + include_examples :search_with_sphinx, :answer + include_examples :search_with_sphinx, :comment + include_examples :search_with_sphinx, :user, :email end end diff --git a/spec/support/shared_examples/controllers/search.rb b/spec/support/shared_examples/controllers/search.rb new file mode 100644 index 0000000..8d3d08d --- /dev/null +++ b/spec/support/shared_examples/controllers/search.rb @@ -0,0 +1,47 @@ +shared_examples :search_with_sphinx do |entity, attribute = :body| + context "searching on #{entity.to_s.capitalize}.#{attribute}" do + context 'with valid search type' do + include_examples :search_with_sphinx_with_search_type, entity, attribute, entity.to_s.first + end + + context 'without search type' do + include_examples :search_with_sphinx_with_search_type, entity, attribute, '' + end + + context 'with invalid search type' do + include_examples :search_with_sphinx_with_search_type, entity, attribute, 'INVALID', false + end + end +end + +shared_examples :search_with_sphinx_with_search_type do |entity, attribute, search_type, validity = true| + let!(:object) { create(entity) } + + if validity + it 'calls SearchService' do + expect(SearchService).to receive(:klass).with(search_type).and_return(object.class) + expect(SearchService).to receive(:search). + with(type: search_type, query: object.send(attribute)).and_call_original + get :search, q: object.send(attribute), t: search_type + end + else + it 'doesn\'t call SearchService' do + expect(SearchService).to_not receive(:klass) + expect(SearchService).to_not receive(:search) + get :search, q: object.send(attribute), t: search_type + end + end + + context 'executing search on empty database' do + before { get :search, q: object.send(attribute), t: search_type } + subject { response } + + it "sets @found to #{validity ? 'empty Array' : 'nil'}" do + get :search, q: object.send(attribute), t: search_type + expect(assigns(:found)).to eq validity ? [] : nil + end + + it { is_expected.to have_http_status validity ? :success : :not_implemented } + it { is_expected.to render_template validity ? :no_results : nil } + end +end From 685d0c8bf28e0cb289d565b34acacfc8e4017917 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sat, 24 Sep 2016 11:53:51 +0300 Subject: [PATCH 11/20] Fix broken star_answer spec because of strange side effect of Sphinx inclusion to Capybara JS execution --- spec/features/star_answer_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/star_answer_spec.rb b/spec/features/star_answer_spec.rb index eb9ec6d..9f2edb0 100644 --- a/spec/features/star_answer_spec.rb +++ b/spec/features/star_answer_spec.rb @@ -41,6 +41,7 @@ second_answer = create(:answer, question: question, user: user) visit question_path(question) within("#answer-#{second_answer.id}") { click_on 'Star' } + sleep 0.1 within "#answers" do expect(page.first('div')[:id]).to eq "answer-#{second_answer.id}" end From b1d98df2324afcaad5e2bebf0108de2de087ddf3 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sat, 24 Sep 2016 15:58:12 +0300 Subject: [PATCH 12/20] Set up test suite for Sphinx, Add search related tests --- spec/controllers/search_controller_spec.rb | 14 +- spec/features/search_spec.rb | 149 +++++------------- spec/features_helper.rb | 4 + .../shared_examples/controllers/search.rb | 47 ------ .../shared_examples/controllers/sphinx.rb | 52 ++++++ .../shared_examples/features/sphinx.rb | 49 ++++++ spec/support/sphinx.rb | 13 +- 7 files changed, 159 insertions(+), 169 deletions(-) delete mode 100644 spec/support/shared_examples/controllers/search.rb create mode 100644 spec/support/shared_examples/controllers/sphinx.rb create mode 100644 spec/support/shared_examples/features/sphinx.rb diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index e83c82a..fe42ca2 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -2,10 +2,14 @@ describe SearchController do describe 'GET #search' do - include_examples :search_with_sphinx, :question, :topic - include_examples :search_with_sphinx, :question - include_examples :search_with_sphinx, :answer - include_examples :search_with_sphinx, :comment - include_examples :search_with_sphinx, :user, :email + include_examples :sphinx_search_controller, :question, :topic + include_examples :sphinx_search_controller, :question + include_examples :sphinx_search_controller, :answer + include_examples :sphinx_search_controller, :comment + include_examples :sphinx_search_controller, :user, :email + + context 'search with invalid search type' do + include_examples :sphinx_search_controller_with_params, :empty, :empty, 'INVALID', false + end end end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index d646db0..86b001c 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -1,118 +1,51 @@ require 'features_helper' -feature 'Search Questions, Answers, Comments and Users', %q( +feature 'Search Questions', %q( To get information I need As a Guest - I want to search information in Questions, Answers, Comments and User emails -) do - given!(:question) { create(:question) } - given!(:answer) { create(:answer) } - given!(:comment) { create(:comment) } - given!(:user) { create(:user) } + I want to search information in Questions +), :sphinx do + given!(:object) { create(:question) } - background { visit questions_path } + include_examples :sphinx_search_feature, :topic +end + +feature 'Search Answers', %q( + To get information I need + As a Guest + I want to search information in Answers +), :sphinx do + given!(:object) { create(:answer) } + + include_examples :sphinx_search_feature +end + +feature 'Search Answers Comments', %q( + To get information I need + As a Guest + I want to search information in Answers Comments +), :sphinx do + given!(:object) { create(:comment, commentable: create(:answer)) } + + include_examples :sphinx_search_feature +end - describe 'In global scope' do - scenario 'search Questions' do - within '#global-search' do - fill_in 'q', with: question.topic - click_button 'Go!' - end +feature 'Search Questions Comments', %q( + To get information I need + As a Guest + I want to search information in Questions Comments +), :sphinx do + given!(:object) { create(:comment, commentable: create(:question)) } - expect(current_path).to eq '/search' + include_examples :sphinx_search_feature +end - expect(page).to have_content question.topic - expect(page).not_to have_content answer.body - expect(page).not_to have_content comment.body - expect(page).not_to have_content user.email - end - end +feature 'Search Users', %q( + To get information I need + As a Guest + I want to search information in Answers +), :sphinx do + given!(:object) { create(:user) } - # scenario 'search Answers' do - # fill_in 'q', with: answer.body - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).not_to have_content user.email - # end - # - # scenario 'search Comments' do - # fill_in 'q', with: comment.body - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).to have_content comment.body - # expect(page).not_to have_content user.email - # end - # - # scenario 'search Users' do - # fill_in 'q', with: user.email - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).to have_content user.email - # end - # - # scenario 'search unsearchable' do - # fill_in 'q', with: 'thetextthatfactorycannotgenerate' - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).not_to have_content user.email - # end - # end - # - # describe 'In typed scope' do - # scenario 'search Questions' do - # fill_in 'q', with: question.topic - # select 'q', from: 'type' - # click_on 'Search' - # expect(page).to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).not_to have_content user.email - # end - # - # scenario 'search Answers' do - # fill_in 'q', with: answer.body - # select 'a', from: 'type' - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).not_to have_content user.email - # end - # - # scenario 'search Comments' do - # fill_in 'q', with: comment.body - # select 't', from: 'type' - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).to have_content comment.body - # expect(page).not_to have_content user.email - # end - # - # scenario 'search Users' do - # fill_in 'q', with: user.email - # select 'u', from: 'type' - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).to have_content user.email - # end - # - # scenario 'search with invalid type' do - # fill_in 'q', with: 'thetextthatfactorycannotgenerate' - # select 'x', from: 'type' - # click_on 'Search' - # expect(page).not_to have_content question.topic - # expect(page).not_to have_content answer.body - # expect(page).not_to have_content comment.body - # expect(page).not_to have_content user.email - # end + include_examples :sphinx_search_feature, :email end diff --git a/spec/features_helper.rb b/spec/features_helper.rb index 3d0faf9..23120a0 100644 --- a/spec/features_helper.rb +++ b/spec/features_helper.rb @@ -31,6 +31,10 @@ DatabaseCleaner.strategy = :truncation end + config.before(:each, :sphinx) do + DatabaseCleaner.strategy = :truncation + end + config.before(:each) do DatabaseCleaner.start end diff --git a/spec/support/shared_examples/controllers/search.rb b/spec/support/shared_examples/controllers/search.rb deleted file mode 100644 index 8d3d08d..0000000 --- a/spec/support/shared_examples/controllers/search.rb +++ /dev/null @@ -1,47 +0,0 @@ -shared_examples :search_with_sphinx do |entity, attribute = :body| - context "searching on #{entity.to_s.capitalize}.#{attribute}" do - context 'with valid search type' do - include_examples :search_with_sphinx_with_search_type, entity, attribute, entity.to_s.first - end - - context 'without search type' do - include_examples :search_with_sphinx_with_search_type, entity, attribute, '' - end - - context 'with invalid search type' do - include_examples :search_with_sphinx_with_search_type, entity, attribute, 'INVALID', false - end - end -end - -shared_examples :search_with_sphinx_with_search_type do |entity, attribute, search_type, validity = true| - let!(:object) { create(entity) } - - if validity - it 'calls SearchService' do - expect(SearchService).to receive(:klass).with(search_type).and_return(object.class) - expect(SearchService).to receive(:search). - with(type: search_type, query: object.send(attribute)).and_call_original - get :search, q: object.send(attribute), t: search_type - end - else - it 'doesn\'t call SearchService' do - expect(SearchService).to_not receive(:klass) - expect(SearchService).to_not receive(:search) - get :search, q: object.send(attribute), t: search_type - end - end - - context 'executing search on empty database' do - before { get :search, q: object.send(attribute), t: search_type } - subject { response } - - it "sets @found to #{validity ? 'empty Array' : 'nil'}" do - get :search, q: object.send(attribute), t: search_type - expect(assigns(:found)).to eq validity ? [] : nil - end - - it { is_expected.to have_http_status validity ? :success : :not_implemented } - it { is_expected.to render_template validity ? :no_results : nil } - end -end diff --git a/spec/support/shared_examples/controllers/sphinx.rb b/spec/support/shared_examples/controllers/sphinx.rb new file mode 100644 index 0000000..690e61b --- /dev/null +++ b/spec/support/shared_examples/controllers/sphinx.rb @@ -0,0 +1,52 @@ +shared_examples :sphinx_search_controller do |entity, attribute = :body| + context 'with type in query' do + include_examples :sphinx_search_controller_with_params, entity, attribute, entity.to_s.first + end + + context 'without type in query' do + include_examples :sphinx_search_controller_with_params, entity, attribute, '' + end +end + +shared_examples :sphinx_search_controller_with_params do |entity, attribute, search_type, validity = true| + if entity == :empty + let!(:object) { '' } + attribute = :to_s + else + let!(:object) { create(entity) } + end + + context "search by #{entity.to_s.capitalize}.#{attribute}" do + if validity + it 'calls SearchService' do + expect(SearchService).to receive(:klass).with(search_type). + and_return(search_type.empty? ? ThinkingSphinx : object.class) + expect(SearchService).to receive(:search). + with(type: search_type, query: object.send(attribute)).and_call_original + get :search, q: object.send(attribute), t: search_type + end + else + it 'doesn\'t call SearchService' do + expect(SearchService).to_not receive(:klass) + expect(SearchService).to_not receive(:search) + get :search, q: object.send(attribute), t: search_type + end + end + end + + context 'execute search on empty database' do + before { get :search, q: object.send(attribute), t: search_type } + subject { response } + + it "sets @found to #{validity ? 'empty Array' : 'nil'}" do + get :search, q: object.send(attribute), t: search_type + expect(assigns(:found)).to eq validity ? [] : nil + end + + it { is_expected.to have_http_status validity ? :success : :not_implemented } + + it("renders #{validity ? ':no_results' : 'nothing'}") do + is_expected.to render_template validity ? :no_results : nil + end + end +end diff --git a/spec/support/shared_examples/features/sphinx.rb b/spec/support/shared_examples/features/sphinx.rb new file mode 100644 index 0000000..6c25fb1 --- /dev/null +++ b/spec/support/shared_examples/features/sphinx.rb @@ -0,0 +1,49 @@ +shared_examples :sphinx_search_feature do |attribute = :body| + given!(:junk_questions) { create_list(:question, 2) } + given!(:junk_answers) { create_list(:answer, 2) } + given!(:junk_comments) { create_list(:comment, 2) } + given!(:junk_users) { create_list(:user, 2) } + + let!(:text) { object.send(attribute) } + + before { visit new_question_path } + + scenario "search with type in query" do + within '#global-search' do + fill_in 'q', with: text + select object.class.name.downcase, from: 't' + click_button 'Go!' + end + + expect(current_path).to eq '/search' + + expect(page).to have_content text + (junk_questions.map(&:topic) + + junk_answers.map(&:body) + + junk_comments.map(&:body) + + junk_users.map(&:email)).each do |junk| + expect(page).to_not have_content junk + end + end + + scenario 'search without type in query' do + within '#global-search' do + fill_in 'q', with: text + click_button 'Go!' + end + + expect(current_path).to eq '/search' + expect(page).to have_content text + end + + scenario 'search unsearchable' do + within '#global-search' do + fill_in 'q', with: 'unsearchablestring' + click_button 'Go!' + end + + expect(current_path).to eq '/search' + expect(page).to_not have_content text + end +end + diff --git a/spec/support/sphinx.rb b/spec/support/sphinx.rb index 0eef5c8..64d905b 100644 --- a/spec/support/sphinx.rb +++ b/spec/support/sphinx.rb @@ -1,8 +1,7 @@ module SphinxHelpers def index ThinkingSphinx::Test.index - # Wait for Sphinx to finish loading in the new index files. - sleep 0.25 until index_finished? + sleep 0.1 until index_finished? end def index_finished? @@ -14,15 +13,11 @@ def index_finished? config.include SphinxHelpers, type: :feature config.before(:suite) do - # Ensure sphinx directories exist for the test environment ThinkingSphinx::Test.init - # Configure and start Sphinx, and automatically - # stop Sphinx at the end of the test suite. ThinkingSphinx::Test.start_with_autostop end - # config.before(:each) do - # # Index data when running an acceptance spec. - # index if example.metadata[:js] - # end + config.before(:each, :sphinx) do + index + end end From f11749d51f708ee7f2751aa3d5133cb252e1ff6b Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sun, 25 Sep 2016 16:09:16 +0300 Subject: [PATCH 13/20] Fix async waiting in star_answer_spec; Clarify test --- spec/features/star_answer_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/features/star_answer_spec.rb b/spec/features/star_answer_spec.rb index 9f2edb0..0b220f0 100644 --- a/spec/features/star_answer_spec.rb +++ b/spec/features/star_answer_spec.rb @@ -41,9 +41,8 @@ second_answer = create(:answer, question: question, user: user) visit question_path(question) within("#answer-#{second_answer.id}") { click_on 'Star' } - sleep 0.1 within "#answers" do - expect(page.first('div')[:id]).to eq "answer-#{second_answer.id}" + expect(page.first('div')).to have_content second_answer.body end end From 961399f8910f401ed554ad462f532c2afbc494a7 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sun, 25 Sep 2016 16:10:03 +0300 Subject: [PATCH 14/20] Move Sphinx search shared examples to tests --- spec/controllers/search_controller_spec.rb | 53 +++++++++++++++++++ spec/features/search_spec.rb | 49 +++++++++++++++++ .../shared_examples/controllers/sphinx.rb | 52 ------------------ .../shared_examples/features/sphinx.rb | 49 ----------------- 4 files changed, 102 insertions(+), 101 deletions(-) delete mode 100644 spec/support/shared_examples/controllers/sphinx.rb delete mode 100644 spec/support/shared_examples/features/sphinx.rb diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index fe42ca2..695c85c 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -1,5 +1,58 @@ require 'rails_helper' +shared_examples :sphinx_search_controller_with_params do |entity, attribute, search_type, validity = true| + if entity == :empty + let!(:object) { '' } + attribute = :to_s + else + let!(:object) { create(entity) } + end + + context "search by #{entity.to_s.capitalize}.#{attribute}" do + if validity + it 'calls SearchService' do + expect(SearchService).to receive(:klass).with(search_type). + and_return(search_type.empty? ? ThinkingSphinx : object.class) + expect(SearchService).to receive(:search). + with(type: search_type, query: object.send(attribute)).and_call_original + get :search, q: object.send(attribute), t: search_type + end + else + it 'doesn\'t call SearchService' do + expect(SearchService).to_not receive(:klass) + expect(SearchService).to_not receive(:search) + get :search, q: object.send(attribute), t: search_type + end + end + end + + context 'execute search on empty database' do + before { get :search, q: object.send(attribute), t: search_type } + subject { response } + + it "sets @found to #{validity ? 'empty Array' : 'nil'}" do + get :search, q: object.send(attribute), t: search_type + expect(assigns(:found)).to eq validity ? [] : nil + end + + it { is_expected.to have_http_status validity ? :success : :not_implemented } + + it("renders #{validity ? ':no_results' : 'nothing'}") do + is_expected.to render_template validity ? :no_results : nil + end + end +end + +shared_examples :sphinx_search_controller do |entity, attribute = :body| + context 'with type in query' do + include_examples :sphinx_search_controller_with_params, entity, attribute, entity.to_s.first + end + + context 'without type in query' do + include_examples :sphinx_search_controller_with_params, entity, attribute, '' + end +end + describe SearchController do describe 'GET #search' do include_examples :sphinx_search_controller, :question, :topic diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 86b001c..5a33a23 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -1,5 +1,54 @@ require 'features_helper' +shared_examples :sphinx_search_feature do |attribute = :body| + given!(:junk_questions) { create_list(:question, 2) } + given!(:junk_answers) { create_list(:answer, 2) } + given!(:junk_comments) { create_list(:comment, 2) } + given!(:junk_users) { create_list(:user, 2) } + + let!(:text) { object.send(attribute) } + + before { visit new_question_path } + + scenario "search with type in query" do + within '#global-search' do + fill_in 'q', with: text + select object.class.name.downcase, from: 't' + click_button 'Go!' + end + + expect(current_path).to eq '/search' + + expect(page).to have_content text + (junk_questions.map(&:topic) + + junk_answers.map(&:body) + + junk_comments.map(&:body) + + junk_users.map(&:email)).each do |junk| + expect(page).to_not have_content junk + end + end + + scenario 'search without type in query' do + within '#global-search' do + fill_in 'q', with: text + click_button 'Go!' + end + + expect(current_path).to eq '/search' + expect(page).to have_content text + end + + scenario 'search unsearchable' do + within '#global-search' do + fill_in 'q', with: 'unsearchablestring' + click_button 'Go!' + end + + expect(current_path).to eq '/search' + expect(page).to_not have_content text + end +end + feature 'Search Questions', %q( To get information I need As a Guest diff --git a/spec/support/shared_examples/controllers/sphinx.rb b/spec/support/shared_examples/controllers/sphinx.rb deleted file mode 100644 index 690e61b..0000000 --- a/spec/support/shared_examples/controllers/sphinx.rb +++ /dev/null @@ -1,52 +0,0 @@ -shared_examples :sphinx_search_controller do |entity, attribute = :body| - context 'with type in query' do - include_examples :sphinx_search_controller_with_params, entity, attribute, entity.to_s.first - end - - context 'without type in query' do - include_examples :sphinx_search_controller_with_params, entity, attribute, '' - end -end - -shared_examples :sphinx_search_controller_with_params do |entity, attribute, search_type, validity = true| - if entity == :empty - let!(:object) { '' } - attribute = :to_s - else - let!(:object) { create(entity) } - end - - context "search by #{entity.to_s.capitalize}.#{attribute}" do - if validity - it 'calls SearchService' do - expect(SearchService).to receive(:klass).with(search_type). - and_return(search_type.empty? ? ThinkingSphinx : object.class) - expect(SearchService).to receive(:search). - with(type: search_type, query: object.send(attribute)).and_call_original - get :search, q: object.send(attribute), t: search_type - end - else - it 'doesn\'t call SearchService' do - expect(SearchService).to_not receive(:klass) - expect(SearchService).to_not receive(:search) - get :search, q: object.send(attribute), t: search_type - end - end - end - - context 'execute search on empty database' do - before { get :search, q: object.send(attribute), t: search_type } - subject { response } - - it "sets @found to #{validity ? 'empty Array' : 'nil'}" do - get :search, q: object.send(attribute), t: search_type - expect(assigns(:found)).to eq validity ? [] : nil - end - - it { is_expected.to have_http_status validity ? :success : :not_implemented } - - it("renders #{validity ? ':no_results' : 'nothing'}") do - is_expected.to render_template validity ? :no_results : nil - end - end -end diff --git a/spec/support/shared_examples/features/sphinx.rb b/spec/support/shared_examples/features/sphinx.rb deleted file mode 100644 index 6c25fb1..0000000 --- a/spec/support/shared_examples/features/sphinx.rb +++ /dev/null @@ -1,49 +0,0 @@ -shared_examples :sphinx_search_feature do |attribute = :body| - given!(:junk_questions) { create_list(:question, 2) } - given!(:junk_answers) { create_list(:answer, 2) } - given!(:junk_comments) { create_list(:comment, 2) } - given!(:junk_users) { create_list(:user, 2) } - - let!(:text) { object.send(attribute) } - - before { visit new_question_path } - - scenario "search with type in query" do - within '#global-search' do - fill_in 'q', with: text - select object.class.name.downcase, from: 't' - click_button 'Go!' - end - - expect(current_path).to eq '/search' - - expect(page).to have_content text - (junk_questions.map(&:topic) + - junk_answers.map(&:body) + - junk_comments.map(&:body) + - junk_users.map(&:email)).each do |junk| - expect(page).to_not have_content junk - end - end - - scenario 'search without type in query' do - within '#global-search' do - fill_in 'q', with: text - click_button 'Go!' - end - - expect(current_path).to eq '/search' - expect(page).to have_content text - end - - scenario 'search unsearchable' do - within '#global-search' do - fill_in 'q', with: 'unsearchablestring' - click_button 'Go!' - end - - expect(current_path).to eq '/search' - expect(page).to_not have_content text - end -end - From 076016c61515de9755fb9bbb69b76d6d54d40835 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sun, 25 Sep 2016 16:12:29 +0300 Subject: [PATCH 15/20] Move search/service to app/services --- app/{models => services}/search_service.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/{models => services}/search_service.rb (100%) diff --git a/app/models/search_service.rb b/app/services/search_service.rb similarity index 100% rename from app/models/search_service.rb rename to app/services/search_service.rb From 812b0fb427da7889e6042879ac21e94523dfbfd4 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Sun, 25 Sep 2016 16:36:33 +0300 Subject: [PATCH 16/20] Change search types to object names --- app/controllers/search_controller.rb | 2 +- app/services/search_service.rb | 21 +++++---------------- app/views/shared/_navbar.slim | 2 +- config/application.rb | 2 ++ spec/controllers/search_controller_spec.rb | 2 +- spec/features/search_spec.rb | 2 +- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index c414293..688d98c 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -11,7 +11,7 @@ def search def get_search_params @search_query = params[:q] @search_type = params[:t].to_s - unless @search_type.blank? || %w(q a c u).include?(@search_type) + unless @search_type.blank? || SearchService::ALLOWED_TYPES.include?(@search_type) @search_type = nil render nothing: true, status: :not_implemented end diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 2823c43..82c28e3 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,4 +1,6 @@ class SearchService + ALLOWED_TYPES = %w(question answer comment user) + class << self def search(params) query = ThinkingSphinx::Query.escape(params[:query] || '') @@ -6,23 +8,10 @@ def search(params) klass(type)&.search(query) end - private - def klass(type) - case type - when '' then - ThinkingSphinx - when 'q' then - Question - when 'a' then - Answer - when 'c' then - Comment - when 'u' then - User - else - nil - end + return ThinkingSphinx if type == '' + return nil unless ALLOWED_TYPES.include?(type) + type.classify.constantize end end end diff --git a/app/views/shared/_navbar.slim b/app/views/shared/_navbar.slim index 85a498b..08c9643 100644 --- a/app/views/shared/_navbar.slim +++ b/app/views/shared/_navbar.slim @@ -32,5 +32,5 @@ nav.navbar.navbar-default role="navigation" .input-group#global-search = search_field_tag :q, @search_query, class: 'form-control', size: 30, placeholder: 'Search for...' span.input-group-btn - = select_tag :t, options_for_select({questions: :q, answers: :a, comments: :c, users: :u}, @search_type), prompt: 'anything ▾', class: 'form-control' + = select_tag :t, options_for_select({questions: :question, answers: :answer, comments: :comment, users: :user}, @search_type), prompt: 'anything ▾', class: 'form-control' button.btn.btn-default type="submit" Go! diff --git a/config/application.rb b/config/application.rb index 3eda4ec..361e34f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,6 +17,8 @@ module DeepRecursion class Application < Rails::Application + config.autoload_paths << Rails.root.join('services') + # Use the responders controller from the responders gem config.app_generators.scaffold_controller :responders_controller diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 695c85c..8fa86e9 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -45,7 +45,7 @@ shared_examples :sphinx_search_controller do |entity, attribute = :body| context 'with type in query' do - include_examples :sphinx_search_controller_with_params, entity, attribute, entity.to_s.first + include_examples :sphinx_search_controller_with_params, entity, attribute, entity.to_s end context 'without type in query' do diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 5a33a23..62ab33f 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -10,7 +10,7 @@ before { visit new_question_path } - scenario "search with type in query" do + scenario 'search with type in query' do within '#global-search' do fill_in 'q', with: text select object.class.name.downcase, from: 't' From 05e76dc31d5aa5750abfce0c7f50ad0b2b508fb8 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Mon, 26 Sep 2016 00:35:54 +0300 Subject: [PATCH 17/20] Add default scopes of model for correct API lists ordering --- app/models/answer.rb | 2 ++ app/models/attachment.rb | 2 ++ app/models/comment.rb | 2 ++ app/models/question.rb | 2 ++ 4 files changed, 8 insertions(+) diff --git a/app/models/answer.rb b/app/models/answer.rb index 2a20c4b..d93cb72 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -11,6 +11,8 @@ class Answer < ActiveRecord::Base validates :user_id, :question_id, presence: true validates :body, presence: true, length: (20..50_000) + default_scope { order({starred: :desc}, :created_at) } + after_commit :invoke_subscriptions_delivery, on: :create def star! diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 21cee8b..1fac4ce 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -4,4 +4,6 @@ class Attachment < ActiveRecord::Base mount_uploader :file, FileUploader validates :file, presence: true + + default_scope { order(:created_at) } end diff --git a/app/models/comment.rb b/app/models/comment.rb index 6908394..3b5bd2c 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -4,4 +4,6 @@ class Comment < ActiveRecord::Base validates :user_id, presence: true validates :body, presence: true, length: (2..2_000) + + default_scope { order(:created_at) } end diff --git a/app/models/question.rb b/app/models/question.rb index b0cd9cd..57b0532 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -13,6 +13,8 @@ class Question < ActiveRecord::Base validates :topic, presence: true, length: (10..200) validates :body, presence: true, length: (20..50_000) + default_scope { order(:created_at) } + after_create :subscribe_author! private From 4deeb64b93f595d673af2e9ee139e9a0b5288544 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Mon, 26 Sep 2016 00:36:29 +0300 Subject: [PATCH 18/20] Revert to delay in 'starred at top' feautre spec --- spec/features/star_answer_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/star_answer_spec.rb b/spec/features/star_answer_spec.rb index 0b220f0..bdeb2fe 100644 --- a/spec/features/star_answer_spec.rb +++ b/spec/features/star_answer_spec.rb @@ -41,6 +41,7 @@ second_answer = create(:answer, question: question, user: user) visit question_path(question) within("#answer-#{second_answer.id}") { click_on 'Star' } + sleep 0.1 within "#answers" do expect(page.first('div')).to have_content second_answer.body end From 73e54115f4524b66db7e43af6f75014884810b6a Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Mon, 26 Sep 2016 01:15:07 +0300 Subject: [PATCH 19/20] Devise confirmation in User factory --- config/initializers/devise.rb | 2 +- spec/factories/users.rb | 2 +- spec/features/sign_up_spec.rb | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index bc19a1e..beb4e87 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -114,7 +114,7 @@ # access will be blocked just in the third day. Default is 0.days, meaning # the user cannot access the website without confirming their account. # config.allow_unconfirmed_access_for = 2.days - config.allow_unconfirmed_access_for = 2.days if Rails.env.test? + # config.allow_unconfirmed_access_for = 2.days if Rails.env.test? # A period that the user is allowed to confirm their account before their # token becomes invalid. For example, if set to 3.days, the user can confirm diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 964f557..e60eee9 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -6,6 +6,6 @@ factory :user do email password '12345678' - password_confirmation '12345678' + confirmed_at {Time.now} end end diff --git a/spec/features/sign_up_spec.rb b/spec/features/sign_up_spec.rb index 6ef1c15..978eee0 100644 --- a/spec/features/sign_up_spec.rb +++ b/spec/features/sign_up_spec.rb @@ -14,6 +14,8 @@ fill_in 'Password confirmation', with: password click_on 'Sign up' - expect(page.find('.alert')).to have_content 'Welcome! You have signed up successfully.' + expect(page.find('.alert')).to have_content Devise.allow_unconfirmed_access_for > 0 ? + 'Welcome! You have signed up successfully.' : + 'A message with a confirmation link has been sent to your email address.' end end From 0ae29e2f8607e8d4255c5e2fe0d90d90261a01e1 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Mon, 26 Sep 2016 17:31:58 +0300 Subject: [PATCH 20/20] Call have_content in 'starred at top' feature spec but cannot get rid of sleep there --- spec/features/star_answer_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/features/star_answer_spec.rb b/spec/features/star_answer_spec.rb index bdeb2fe..2db44bc 100644 --- a/spec/features/star_answer_spec.rb +++ b/spec/features/star_answer_spec.rb @@ -42,7 +42,8 @@ visit question_path(question) within("#answer-#{second_answer.id}") { click_on 'Star' } sleep 0.1 - within "#answers" do + expect(page).to have_content second_answer.body + within '#answers' do expect(page.first('div')).to have_content second_answer.body end end