From 3cd27d29bb5d4a66c244f1ac336cbfefbfe06071 Mon Sep 17 00:00:00 2001 From: Oreoluwa Akinniranye Date: Sat, 12 Mar 2016 11:35:04 +0100 Subject: [PATCH 1/4] started working on the actual search and enhancing the search results --- app/controllers/questions_controller.rb | 4 +++ app/models/concerns/searchable.rb | 19 +++++++++- app/models/tag.rb | 9 +++-- app/views/questions/_question.json.jbuilder | 4 ++- .../questions/_search_result.json.jbuilder | 5 +++ app/views/questions/search.json.jbuilder | 3 ++ app/views/users/_user.json.jbuilder | 2 +- config/routes.rb | 3 ++ db/seeds.rb | 36 ++++++++++++++++++- 9 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 app/views/questions/_search_result.json.jbuilder create mode 100644 app/views/questions/search.json.jbuilder diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 2b7411a..fdf418f 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -41,6 +41,10 @@ def top_questions render :index end + def search + @questions = Question.search(params[:q]) + end + private def set_question diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 51e015a..79081ad 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -5,14 +5,31 @@ module Searchable include Elasticsearch::Model # include Elasticsearch::Model::Callbacks + settings analysis: { + tokenizer: { + zhishi_edge_ngram_tokenizer: { + type: :edgeNGram, + min_gram: 3, + max_gram: 20, + token_chars: [ :letter, :digit ], + } + }, + analyzer: { + zhishi_edge_ngram_analyzer: { + tokenizer: :zhishi_edge_ngram_tokenizer + }, + } + } + after_touch :index_document_with_elastic_job after_save :index_document_with_elastic_job after_destroy :delete_document_from_elastic_with_job + def self.custom_index_name [Rails.application.class.parent_name.downcase, Rails.env, model_name.collection.gsub('-', '')].join('_') end - + index_name custom_index_name end diff --git a/app/models/tag.rb b/app/models/tag.rb index 5b8b51b..8501491 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -1,4 +1,6 @@ class Tag < ActiveRecord::Base + include Searchable + validates :name, presence: true has_many :resource_tags has_many :questions, through: :resource_tags, source: :taggable, source_type: 'Question' @@ -15,8 +17,9 @@ def self.get_tags_that_are(arg) end end - scope :search, -> (arg) do - val = arg ? where(arel_table[:name].matches("%#{arg}%")) : all - val.limit(5) + def as_indexed_json(options={}) + self.as_json( + only: [:name] + ) end end diff --git a/app/views/questions/_question.json.jbuilder b/app/views/questions/_question.json.jbuilder index fddf305..c9d2045 100644 --- a/app/views/questions/_question.json.jbuilder +++ b/app/views/questions/_question.json.jbuilder @@ -1,3 +1,5 @@ json.extract! question, :id, :title, :content, :votes_count ,:answers_count, :comments_count, :views, :created_at, :updated_at -json.partial! 'users/user', user: question.user +json.user do + json.partial! 'users/user', user: question.user +end json.url question_url(question) diff --git a/app/views/questions/_search_result.json.jbuilder b/app/views/questions/_search_result.json.jbuilder new file mode 100644 index 0000000..2f0228f --- /dev/null +++ b/app/views/questions/_search_result.json.jbuilder @@ -0,0 +1,5 @@ +json.extract! question, :id, :title, :content +json.user do + json.extract! question.user, :name, :email +end +json.url question_url(question.id) diff --git a/app/views/questions/search.json.jbuilder b/app/views/questions/search.json.jbuilder new file mode 100644 index 0000000..3df4d07 --- /dev/null +++ b/app/views/questions/search.json.jbuilder @@ -0,0 +1,3 @@ +json.questions(@questions) do |question| + json.partial! 'questions/search_result', question: question +end diff --git a/app/views/users/_user.json.jbuilder b/app/views/users/_user.json.jbuilder index b958fb8..1419bd3 100644 --- a/app/views/users/_user.json.jbuilder +++ b/app/views/users/_user.json.jbuilder @@ -1 +1 @@ -json.user user, :id, :name, :email, :points, :image +json.extract! user, :id, :name, :email, :points, :image diff --git a/config/routes.rb b/config/routes.rb index 0c15beb..46ea6a8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,9 @@ post '/validate_token', to: 'tokens#validate' resources :questions, except: [:new, :edit] do + collection do + get :search + end member do get "recent_answers" get "popular_answers" diff --git a/db/seeds.rb b/db/seeds.rb index c3d7c72..1cff74a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,6 +6,40 @@ # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +FakeOmniAuth = Struct.new(:uid, :provider) do + Info = Struct.new(:name, :email, :image, :urls) + Credential = Struct.new(:token, :refresh_token) + + def info + user_credentials = { + name: name, + email: Faker::Internet.email.gsub(/@.+/, '@andela.com'), + image: Faker::Avatar.image(name), + url: { + Google: Faker::Internet.url, + } + } + Info.new(*user_credentials.values) + end + + def name + @name ||= Faker::Name.first_name + end + + def credentials + token = SecureRandom.uuid.gsub('-', '') + Credential.new(token, nil) + end +end + +24.times{ + email = Faker::Internet.email.gsub(/@.+/, '@andela.com') + name = Faker::Name.first_name + id_number = Faker::IDNumber.valid + + User.from_omniauth(FakeOmniAuth.new(id_number, 'google-oauth2')) +} + questions_list =[ ["Requirements for Amity", "What are the requirements for geting accommodation at Amity" ], ["Renewing Amity Contract", "What happens at the expiration of the 6 months Amity resident agreement? Is it automatically renewed or are there processes to follow to get it renew. In same vein what steps are required if someone wants to leave before the expiration of the agreement."], @@ -22,6 +56,6 @@ ["What is the whole purpose of simulations project?", "What is the whole purpose of simulations project when the checkpoints do a better job of enabling fellows to learn things faster?"] ] -questions_list.each do |title, content, user_id| +questions_list.each do |title, content| Question.create(title: title, content: content, user: User.order('RANDOM()').limit(1).first) end From cce4c6797b1558668a0dcbd149b95765d6aeeabf Mon Sep 17 00:00:00 2001 From: Oreoluwa Akinniranye Date: Sat, 12 Mar 2016 13:10:22 +0100 Subject: [PATCH 2/4] set json as default global format --- config/routes.rb | 76 +++++++++++++++++++------------------ docs/questions_endpoints.md | 64 +++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 41 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 46ea6a8..a8df3e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,53 +2,55 @@ require 'sidekiq-status/web' Rails.application.routes.draw do - scope '/:resource_name/:resource_id', constraints: { resource_name: /(questions|answers|comments)/ } do - post '/upvote', to: 'votes#upvote' - post '/downvote', to: 'votes#downvote' - resources :comments, except: [:new, :edit], resource_name: /(?!comments).*/ - end - - post '/validate_token', to: 'tokens#validate' - - resources :questions, except: [:new, :edit] do - collection do - get :search - end - member do - get "recent_answers" - get "popular_answers" + constraints format: :json do + scope '/:resource_name/:resource_id', constraints: { resource_name: /(questions|answers|comments)/ } do + post '/upvote', to: 'votes#upvote' + post '/downvote', to: 'votes#downvote' + resources :comments, except: [:new, :edit], resource_name: /(?!comments).*/ end - resources :answers, except: [:new, :edit] - end + post '/validate_token', to: 'tokens#validate' - get "top_questions" => "questions#top_questions" + resources :questions, except: [:new, :edit] do + collection do + get :search + end + member do + get "recent_answers" + get "popular_answers" + end - resources :users, only: [:show, :index] do - member do - get :questions - get :tags + resources :answers, except: [:new, :edit] end - collection do - post :logout - get :renew_token + get "top_questions" => "questions#top_questions" + + resources :users, only: [:show, :index] do + member do + get :questions + get :tags + end + + collection do + post :logout + get :renew_token + end end - end - resources :tags, only: [:index] do - collection do - get :popular - get :recent - get :trending + resources :tags, only: [:index] do + collection do + get :popular + get :recent + get :trending + end end - end - get 'login', to: "users#login" - get 'login/:provider', to: "users#login" + get 'login', to: "users#login" + get 'login/:provider', to: "users#login" - get 'auth/:provider/callback', to: 'users#authenticate', as: :authenticate - mount Sidekiq::Web => '/sidekiq' + get 'auth/:provider/callback', to: 'users#authenticate', as: :authenticate + mount Sidekiq::Web => '/sidekiq' - match "*path", to: 'application#resource_not_found', via: :all + match "*path", to: 'application#resource_not_found', via: :all + end end diff --git a/docs/questions_endpoints.md b/docs/questions_endpoints.md index 7314677..db5922d 100644 --- a/docs/questions_endpoints.md +++ b/docs/questions_endpoints.md @@ -8,6 +8,7 @@ GET /questions/:id |question's id, auth_token in header| Returns the question wi GET questions/top_questions | offset, limit ( both could be optional ), auth_token in header | Returns the questions matching the criteria for top questions or error message if any. PUT questions/:id| question's id, update information(title, description), auth_token in header | Returns the updated question and all the information concerning it or error message if any. DELETE questions/:id| question's id, auth_token in header | Returns a confirmation that the question has been deleted or error message if any. +GET questions/search | `q` which has the value of the params to search | Returns an array of questions, with a few things stripped off, such as association counts etc ## GET /questions/ Request @@ -22,10 +23,8 @@ Status: 200 id: 1, title: "what is Andela?", user_id: 1, - tags: [{ - matches: [ "operations", "Andela"], - }], - } + tags: [ "operations", "Andela"], + }, { id: 2 title: "where is Amity?" @@ -316,3 +315,60 @@ Status: 403 message: Error Message } ``` + + +## GET /questions/search +Request +```ruby + GET /questions/search?q=search+params +``` +Response +```ruby +Status: 200 + { + questions:[{ + id: 1, + title: "what is Andela?", + content: "Andela has been said to be everything" + user: { + name: 'User Name', + email: 'username@email.com', + }, + tags: [ "operations", "Andela"], + url: 'http://zhishi.com/questions/1', + } + { + id: 2 + title: "where is Amity?" + user_id: 12 + tags: [{ + matches: [ "operations", "Andela"], + }], + } + { + id: 3 + title: "what is M55?" + user_id: 4 + tags: [{ + matches: [ "operations", "Andela"], + }], + } + { + id: 4 + title: "What is DevOps?" + user_id: 12 + tags: [{ + matches: [ "operations", "Andela"], + }], + } + { + id: 5 + title: "What is month one all about?" + user_id: 12 + tags: [{ + matches: [ "operations", "Andela"], + }], + } + ] + } +``` From 1f73f7bd53ac2959eb9826baf0dde3f1adc3e232 Mon Sep 17 00:00:00 2001 From: Oreoluwa Akinniranye Date: Sat, 12 Mar 2016 15:04:24 +0100 Subject: [PATCH 3/4] fixed user nesting --- app/views/answers/_answer.json.jbuilder | 4 +- app/views/comments/_comment.json.jbuilder | 2 +- app/views/comments/_default.json.jbuilder | 4 +- app/views/questions/show.json.jbuilder | 1 + config/routes.rb | 76 +++++++++++------------ 5 files changed, 45 insertions(+), 42 deletions(-) diff --git a/app/views/answers/_answer.json.jbuilder b/app/views/answers/_answer.json.jbuilder index 9c6f52b..eb0b223 100644 --- a/app/views/answers/_answer.json.jbuilder +++ b/app/views/answers/_answer.json.jbuilder @@ -2,6 +2,8 @@ json.answers(answers) do |answer| json.partial! 'comments/default', data: answer json.extract! answer, :question_id json.extract! answer, :comments_count - json.partial! 'users/user', user: answer.user + json.user do + json.partial! 'users/user', user: answer.user + end json.partial! 'comments/comment', comments: answer.comments end diff --git a/app/views/comments/_comment.json.jbuilder b/app/views/comments/_comment.json.jbuilder index 0c5499f..3ef6a20 100644 --- a/app/views/comments/_comment.json.jbuilder +++ b/app/views/comments/_comment.json.jbuilder @@ -1 +1 @@ - json.comments @comment, partial: 'comments/default', as: :data + json.comments comments, partial: 'comments/default', as: :data diff --git a/app/views/comments/_default.json.jbuilder b/app/views/comments/_default.json.jbuilder index 8b1499d..57b9c95 100644 --- a/app/views/comments/_default.json.jbuilder +++ b/app/views/comments/_default.json.jbuilder @@ -1,2 +1,4 @@ json.(data, :id, :content, :votes_count, :created_at, :updated_at) -json.partial! 'users/user', user: data.user +json.user do + json.partial! 'users/user', user: data.user +end diff --git a/app/views/questions/show.json.jbuilder b/app/views/questions/show.json.jbuilder index 2beafa8..e2914e2 100644 --- a/app/views/questions/show.json.jbuilder +++ b/app/views/questions/show.json.jbuilder @@ -1,4 +1,5 @@ json.partial! 'questions/question', question: @question json.partial! 'tags/tag', tags: @question.tags_to_a json.partial! 'answers/answer', answers: @question.answers +# require 'pry' ; binding.pry json.partial! 'comments/comment', comments: @question.comments diff --git a/config/routes.rb b/config/routes.rb index a8df3e0..46ea6a8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,55 +2,53 @@ require 'sidekiq-status/web' Rails.application.routes.draw do - constraints format: :json do - scope '/:resource_name/:resource_id', constraints: { resource_name: /(questions|answers|comments)/ } do - post '/upvote', to: 'votes#upvote' - post '/downvote', to: 'votes#downvote' - resources :comments, except: [:new, :edit], resource_name: /(?!comments).*/ - end - - post '/validate_token', to: 'tokens#validate' + scope '/:resource_name/:resource_id', constraints: { resource_name: /(questions|answers|comments)/ } do + post '/upvote', to: 'votes#upvote' + post '/downvote', to: 'votes#downvote' + resources :comments, except: [:new, :edit], resource_name: /(?!comments).*/ + end - resources :questions, except: [:new, :edit] do - collection do - get :search - end - member do - get "recent_answers" - get "popular_answers" - end + post '/validate_token', to: 'tokens#validate' - resources :answers, except: [:new, :edit] + resources :questions, except: [:new, :edit] do + collection do + get :search end + member do + get "recent_answers" + get "popular_answers" + end + + resources :answers, except: [:new, :edit] + end - get "top_questions" => "questions#top_questions" + get "top_questions" => "questions#top_questions" - resources :users, only: [:show, :index] do - member do - get :questions - get :tags - end + resources :users, only: [:show, :index] do + member do + get :questions + get :tags + end - collection do - post :logout - get :renew_token - end + collection do + post :logout + get :renew_token end + end - resources :tags, only: [:index] do - collection do - get :popular - get :recent - get :trending - end + resources :tags, only: [:index] do + collection do + get :popular + get :recent + get :trending end + end - get 'login', to: "users#login" - get 'login/:provider', to: "users#login" + get 'login', to: "users#login" + get 'login/:provider', to: "users#login" - get 'auth/:provider/callback', to: 'users#authenticate', as: :authenticate - mount Sidekiq::Web => '/sidekiq' + get 'auth/:provider/callback', to: 'users#authenticate', as: :authenticate + mount Sidekiq::Web => '/sidekiq' - match "*path", to: 'application#resource_not_found', via: :all - end + match "*path", to: 'application#resource_not_found', via: :all end From 8e9d7b301860c8ae35d95f92afeae01964c9015e Mon Sep 17 00:00:00 2001 From: Oreoluwa Akinniranye Date: Sat, 12 Mar 2016 15:39:08 +0100 Subject: [PATCH 4/4] refactored to skip querying for votes value of the total votes is present --- app/models/concerns/votes_counter.rb | 6 +++++- config/initializers/rack_timeout.rb | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/votes_counter.rb b/app/models/concerns/votes_counter.rb index 5ec7ac9..fbe578f 100644 --- a/app/models/concerns/votes_counter.rb +++ b/app/models/concerns/votes_counter.rb @@ -16,7 +16,11 @@ module VotesCounter end def votes_count - try(:total_votes) || votes_alternative + if respond_to? :total_votes + total_votes.to_i + else + votes_alternative + end end def votes_alternative diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb index 96f2461..9db092b 100644 --- a/config/initializers/rack_timeout.rb +++ b/config/initializers/rack_timeout.rb @@ -1 +1,3 @@ -Rack::Timeout.timeout = 20 # seconds +if Rails.env.production? + Rack::Timeout.timeout = 20 # seconds +end