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/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/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/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/_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/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/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/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 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 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"], + }], + } + ] + } +```