Find objects through multiple conditions in rails - ruby-on-rails

For a quiz application, I am trying to display ACTIVE questions, in which the current user has not answered. The answer table has a user_id and question_id that I'm trying to use. I thought I was close with the following query (in the static_pages_controller.rb:
#questions = Question.active_question.where.not(id: #user.answers)
It seems to not work in all situations when I am testing it though. I feel like I'm close, but not sure where to go from here. Pretty new to Rails so greatly appreciate any assistance!
question.rb
has_many :answers
scope :active_questions, -> { where("? BETWEEN start AND end", Time.now.to_date)}
scope :activeAtDate, lambda{ |date = Date.today| where("? BETWEEN start AND end", date) }
answer.rb
belongs_to :question
belongs_to :user
scope :user_answered, lambda {|q| where("question_id == (?)", q) }
validates_uniqueness_of :question_id, scope: :user_id
user.rb
has_many :answers
static_pages_controller.rb
def index
#user = current_user
if user_signed_in?
if #user.manager_id != nil
#manager = #user.manager_id
end
#my_team = User.where("manager_id = ? AND id != ?", #manager, #user.id)
#questions = Question.active_question.where.not(id: #user.answers)
end
#new_answer = Answer.new
end

Pretty new to Rails
Welcome - you've done very well so far.
I originally thought you should be calling the questions from your #user.answers association - then I realized you wanted questions to which the user had not answered.
...so I'd do the following:
#app/models/user.rb
class User < ActiveRecord::Base
def non_participating_questions
answered_questions = self.answers.pluck(:question_id)
Question.active_questions.where.not(id: answered_questions)
end
end
This will allow you to call:
#user = User.find params[:id]
#questions = #user.non_participating_questions
The benefits of attaching this to the User model is that you can use the instance_method (so you don't have to pass #user each time). It also means you're able to deal with all the associative data a user has as well (answers etc).

Related

How to implement retweet functionality in RoR?

I'm trying to implement retweet functionality on my app.
So I have my retweet_id in my tweets model
tweets schema
| user_id | content | created_at | updated_at | retweet_id
tweets.rb
belongs_to :user
has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
user.rb
has_many :tweets
And in my tweets controller
tweets_controller.rb
...
def retweet
#retweet = Tweet.new(retweet_params)
if #retweet.save
redirect_to tweet_path, alert: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
Private
...
def retweet_params
params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
end
In my view
tweets/show.html.erb
<%= link_to 'Retweet', retweet_tweet_path(#tweet.id), method: :post %>
My routes
resources :tweets do
resources :comments
resources :likes
member do
post :retweet
end
end
So when I try this I get an error
param is missing or the value is empty: retweet
So I remove .require from 'retweet_params' and that removes that error (though i'm unsure of how wise that is)
Then the link works but won't retweet - reverting to the fallback root_path specified in my action instead.
Unpermitted parameters: :_method, :authenticity_token, :id
Redirected to http://localhost:3000/
I'm not sure what i'm doing wrong. How can I get my retweets working? ty
The reason retweet_params raises an error is because your link link_to 'Retweet', retweet_tweet_path(#tweet.id), method: :post doesn't contain parameters like a new or edit form does. Instead you should create a new tweet that reference to tweet you want to retweet.
before_action :set_tweet, only: %i[show edit update destroy retweet]
def retweet
retweet = #tweet.retweets.build(user: current_user)
if retweet.save
redirect_to retweet, notice: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
private
def set_tweet
#tweet = Tweet.find(params[:id])
end
The above should automatically link the new tweet to the "parent". If this doesn't work for some reason you could manually set it by changing the above to:
retrweet = Tweet.new(retweet_id: #tweet.id, user: current_user)
The above approach doesn't save any content, since this is a retweet.
If you don't want to allow multiple retweets of the same tweet by the same user, make sure you have the appropriate constraints and validations set.
# migration
add_index :tweets, %i[user_id retweet_id], unique: true
# model
validates :retweet_id, uniqueness: { scope: :user_id }
How do we access the content of a retweet? The answer is we get the content form the parent or source (however you want to call it).
There is currently no association that lets you access the parent or source tweet. You currently already have:
has_many :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
To easily access the source content let's first add an additional association.
belongs_to :source_tweet, optional: true, inverse_of: :retweets, class_name: 'Tweet', foreign_key: 'retweet_id'
has_many :retweets, inverse_of: :source_tweet, class_name: 'Tweet', foreign_key: 'retweet_id'
With the above associations being set we can override the content getter and setter of the Tweet model.
def content
if source_tweet
source_tweet.content
else
super
end
end
def content=(content)
if source_tweet
raise 'retweets cannot have content'
else
super
end
end
# depending on preference the setter could also be written as validation
validates :content, absence: true, if: :source_tweet
Note that the above is not efficient when talking about query speed, but it's the easiest most clear solution. Solving parent/child queries is sufficiently difficult that it should get its own question, if speed becomes an issue.
If you are wondering why I set the inverse_of option. I would recommend you to check out the section Active Record Associations - 3.5 Bi-directional Associations.
Right now the error you're seeing is the one for strong params in Rails. If you can check your debugger or the HTTP post request that's being sent, you'd find that you don't have the params that you're "requiring" in retweet_params
def retweet_params
params.require(:retweet).permit(:retweet_id, :content).merge(user_id: current_user.id)
end
This is essentially saying that you expect a nested hash for the params like so
params = { retweet: { id: 1, content: 'Tweet' } }
This won't work since you're only sending the ID. How about something like this instead?
TweetsController.rb
class TweetsController < ApplicationController
def retweet
original_tweet = Tweet.find(params[:id])
#retweet = Tweet.new(
user_id: current_user.id,
content: original_tweet.content
)
if #retweet.save
redirect_to tweet_path, alert: 'Retweeted!'
else
redirect_to root_path, alert: 'Can not retweet'
end
end
end

Questions & Answer - User Input not accepted

I am building a question and answer website where the user inputs a question, I take the users input and compare it against the database, If the answer is correct, I take the value of the question and add it to the scoreboard, However I am having an issue where the users input is not being passed through the models, and when I set the #user_input = params[:content], It is always returning null, I believe this is because I am using namespace/nested models.
The plan of actions:
Get the current answer from the database for the current_question_id, and set as a varaiable
Check Users input against the answer in the database
if question_id & user_submitted_answer exists in database
create record in submission's table with status == correct
else
create record in submissions table with status == Incorrect
Models:
members/question
class Members::Question < ActiveRecord::Base
belongs_to :category
belongs_to :point
belongs_to :event
has_many :submissions
has_one :answer
end
Submissions
class Submission < ActiveRecord::Base
belongs_to :question
belongs_to :member
end
Routes:
Rails.application.routes.draw do
#Root Routes
root 'home#index'
get 'home/index'
get 'home/about'
mount RailsAdmin::Engine => '/eriadmin', as: 'rails_admin'
#Devise Controller
devise_for :members
#Members namepsace
get '/members/', to: 'members/dashboard#index', as: 'members_dashboard', only: [:show]
resources :answers
namespace :members do
resources :questions, only: [:index,:show] do
resources :submissions
end
end
end
Submissions Controller
Create Method
#answer_check = Answer.where("question_id = ? AND content = ?",set_members_question, set_user_submission)
if #answer_check.present?
flash.alert = 'Answer There'
#submission = #members_question.submissions.create(params[:submission].permit(:question_id))
#submission.member_id = current_member.id
#submission.content = Submission.find_by(params[:content])
else
flash.alert = 'NOPE NOT There'
#submission = #members_question.submissions.create(params[:submission].permit(:question_id))
#submission.content = "Incorrect"
#submission.member_id = current_member.id
end
def set_user_submission
#members_question = Submission.find(params[:content])
end
The user_submitted_answer :content is not being passed from the form in the "Questions/Show" into the submissions controller?
Am I doing this wrong, approaching it wrong or is there a rails method that I can use that is more efficient and works to achieve what I need?
My current code, Always returns false and the users_input is not being inserted into the database.
From what I see, you are creating the submission in either of the cases, then assigning the content and member id and not saving it.
Keep in mind that create method initializes the object and also saves it after which any values you assign needs to be saved again. So I have tweaked your code as follows where the submission object is initialized then you add the content and member_id and atlast you save it.
if #answer_check.present?
flash.alert = 'Answer There'
#submission = #members_question.submissions.new(params[:submission].permit(:question_id))
#submission.member_id = current_member.id
#submission.content = Submission.find_by(params[:content])
#submission.save
else
flash.alert = 'NOPE NOT There'
#submission = #members_question.submissions.new(params[:submission].permit(:question_id))
#submission.content = "Incorrect"
#submission.member_id = current_member.id
#submission.save
end
Hope this works and please upvote if deemed correct!
Cheers!

Using Active Record Reputation System gem, no sorting happens when I sort by votes

Following the RailsCast for the reputation system gem, I added the following code to my microposts_controller
def index
#microposts = Micropost.paginate(page: params[:page]).find_with_reputation(:votes, :all, order: "votes desc")
#micropost = current_user.microposts.build
end
But no sorting happens in my index action aside from the default scope I set in my model
In my micropost model I have
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :retweets
has_reputation :votes, source: :user, aggregated_by: :sum
default_scope -> { order('created_at DESC') }
If I change the default scope to
default_scope -> { order('votes DESC') }
It works how I want it to for the index page only but breaks all of my other pages.
I tried removing the default scope and leaving in the find_with_reputation method but it still doesn't order by votes.
I also tried defining the scope in a method in the micropost model like this
def self.popular
find_with_reputation(:votes, :all, {:order => 'votes desc'})
end
And make the code in the microposts_controller like this
def index
#microposts = Micropost.paginate(page: params[:page]).popular
#micropost = current_user.microposts.build
end
It still does not sort by votes.
Here is a copy of the log output from visiting the micropost index page
https://gist.github.com/anonymous/9745552
Here is a link to the gem https://github.com/NARKOZ/activerecord-reputation-system/tree/rails4
My routes.rb for microposts looks like this
resources :microposts, only: [:create, :destroy, :index] do
member { post :vote }
member { post :retweet}
end
Any guidance is appreciated.
Update
My home page feed is designed differently from what I'm doing for the Micropost Index feed. Maybe comparing what works to what doesn't will help pinpoint the issue.
I have a Static Pages Controller which sets its scope for the home action like this
def home
#micropost = current_user.microposts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
In the user model I define the feed method used in the static pages controller like so
def feed
Micropost.from_users_followed_by_including_replies(self)
end
the from_users_followed_by_including_replies(self) method is a scope i set in the micropost model
scope :from_users_followed_by_including_replies, lambda { |user| followed_by_including_replies(user) }
def self.followed_by_including_replies(user)
followed_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{followed_ids}) OR user_id = :user_id OR to_id = :user_id",
{ :user_id => user })
end
Maybe I need to adapt a similar approach to the Index action for the Microposts controller
EDIT
In getting my hands on the code, I've found the real problem stems from the use of default_scope.
The original order() clause specified in your default scope is still being applied, even when adding your own order().
As a side note, this issue was kind of fixed in Rails 4.0, but the behavior was reverted in 4.0.1.
The solution was to apply a reorder()
# model
def self.popular
reorder('votes desc').find_with_reputation(:votes, :all)
end
# controller
def index
#microposts = Micropost.page(params[:page]).popular
end
ORIGINAL ANSWER
It seems that using the paginate method directly may not work with activerecord-reputation-system,
However, I found some examples showing that you can use the will_paginate page and per methods:
Perhaps it will work like this:
Micropost.page(params[:page]).per(30).find_with_reputation(:votes, :all, order: "votes desc")
Or with the model scope like this:
def self.popular
find_with_reputation(:votes, :all, order: 'votes desc')
end
you could do this:
Micropost.page(params[:page]).per(30).popular
Also, as a side note, your routes file is a little strange with multiple member blocks, when only one is necessary. I would make it look like this:
resources :microposts, only: [:create, :destroy, :index] do
member do
post :vote
post :retweet
end
end

ActiveRecord::RecordNotFound - in a descendant class' associated_controller#index

I am attempting to locate a parent object in a nested controller, so that I can associate the descendant resource with the parent like so:
# teams_controller.rb <snippet only>
def index
#university = Univeresity.find(params[:university_id])
#teams = #university.teams
end
When I call find(params[:university_id]) per the snippet above & in line 6 of teams_controller.rb, I receive ActiveRecord::RecordNotFound - Couldn't find University without an ID.
I'm not only interested in fixing this issue, but would also enjoy a better understanding of finding objects without having to enter a University.find(1) value, since I grant Admin the privilege of adding universities.
The Rails Guides say the following about the two kinds of parameters in a website:
3 Parameters
You will probably want to access data sent in by the user or other
parameters in your controller actions. There are two kinds of
parameters possible in a web application. The first are parameters
that are sent as part of the URL, called query string parameters. The
query string is everything after “?” in the URL. The second type of
parameter is usually referred to as POST data. This information
usually comes from an HTML form which has been filled in by the user.
It’s called POST data because it can only be sent as part of an HTTP
POST request. Rails does not make any distinction between query string
parameters and POST parameters, and both are available in the params
hash in your controller:
It continues a little further down, explaining that the params hash is an instance of HashWithIndifferentAccess, which allows usage of both symbols and strings interchangeably for the keys.
From what I read above, my understanding is that Rails recognizes both parameters (URL & POST) and stores them in the same hash (params).
Can I pass the params hash into a find method in any controller action, or just the create/update actions? I'd also be interested in finding a readable/viewable resource to understand the update_attributes method thats called in a controller's 'update' action.
Please overlook the commented out code, as I am actively searching for answers as well.
Thanks in advance.
Here are the associated files and server log.
Webrick
teams_controller.rb
class TeamsController < ApplicationController
# before_filter :get_university
# before_filter :get_team
def index
#university = University.find(params[:univeristy_id])
#teams = #university.teams
end
def new
#university = University.find(params[:university_id])
#team = #university.teams.build
end
def create
#university = University.find(params[:university_id])
#team = #university.teams.build(params[:team])
if #team.save
redirect_to [#university, #team], success: 'Team created!'
else
render :new, error: 'There was an error processing your team'
end
end
def show
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
end
def edit
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
end
def update
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
if #team.update_attributes(params[:team])
redirect_to([#university, #team], success: 'Team successfully updated')
else
render(:edit, error: 'There was an error updating your team')
end
end
def destroy
#university = University.find(params[:university_id])
#team = #university.teams.find(params[:id])
#team.destroy
redirect_to university_teams_path(#university)
end
private
def get_university
#university = University.find(params[:university_id]) # can't find object without id
end
def get_team
#team = #university.teams.find(params[:id])
end
end
team.rb
class Team < ActiveRecord::Base
attr_accessible :name, :sport_type, :university_id
has_many :home_events, foreign_key: :home_team_id, class_name: 'Event'
has_many :away_events, foreign_key: :away_team_id, class_name: 'Event'
has_many :medias, as: :mediable
belongs_to :university
validates_presence_of :name, :sport_type
# scope :by_university, ->(university_id) { where(team_id: team_id).order(name: name) }
# scope :find_team, -> { Team.find_by id: id }
# scope :by_sport_type, ->(sport_type) { Team.where(sport_type: sport_type) }
# scope :with_university, joins: :teams
# def self.by_university(university_id)
# University.where(id: 1)
# University.joins(:teams).where(teams: { name: name })
# end
def self.by_university
University.where(university_id: university_id).first
end
def self.university_join
University.joins(:teams)
end
def self.by_sport_type(sport_type)
where(sport_type: sport_type)
end
def self.baseball
by_sport_type('Baseball/Softball')
end
end
university.rb
class University < ActiveRecord::Base
attr_accessible :address, :city, :name, :state, :url, :zip
has_many :teams, dependent: :destroy
validates :zip, presence: true, format: { with: /\A\d{5}(-\d+)?\z/ },
length: { minimum: 5 }
validates_presence_of :name, :address, :city, :state, :url
scope :universities, -> { University.order(name: 'ASC') }
# scope :by_teams, ->(university_id) { Team.find_by_university_id(university_id) }
# scope :team_by_university, ->(team_id) { where(team_id: team_id).order(name: name)}
def sport_type
team.sport_type
end
end
views/teams/index.html.erb
Placed in gists for formatting reasons
rake routes output: (in a public gist)
enter link description here
rails console
You're not going to want to have both:
resources :universities #lose this one
resources :universities do
resources :teams
end
As for params... you have to give a param. So, when you go to http://localhost:3000/teams there are no params, by default. If you go to http://localhost:3000/teams/3 then params[:id] = 3 and this will pull up your third team.
Keep in mind the nomenclature of an index. The index action of Teams, is going to list all of the teams. All of them. There is no one University there, so what are you actually trying to find? If anything, you'd have, for your University controller:
def show
#university = University.find(params[:id])
#teams = #university.teams
end
so, the address bar will be showing http://localhost:3000/universities/23, right? params[:id] = 23, then you can find the teams associated with that university.

Moving Logic to Model

In my Rails 4 app I am starting to move my logic to the model (working on the Fat Model principle). However, I'm a little unsure on how best to tackle the basics.
My app displays bookings for the logged in user. In my BookingsController I have an action that displays the confirmed bookings:
def confirmed
#bookings = Booking.where(:status => 3, :accommodation_id => current_user.accommodation.id).order('updated_at DESC')
end
However, I have tried to move the logic to the controller so that the BookingsController now looks like this:
def confirmed
#bookings = Booking.confirmed_bookings
end
and the Booking model looks like this:
def confirmed_bookings
bookings = where(:status => 3, :accommodation_id => current_user.accommodation.id).order('updated_at DESC')
end
I am receiving an undefined method `confirmed_bookings' error and not sure what I'm doing wrong. A fix for this and some simple "starter" advice would be much appreciated.
Split your scopes in reusable chunks:
scope :confirmed, ->{ where(:status => 3) }
scope :for_user, ->(user) { where(:accommodation_id => user.accommodation.id) }
def self.confirmed_bookings_for(user)
confirmed.for_user(user).order('updated_at DESC')
end
Notice, it' would be much better not to refer to the user at all, since it's for an accommodation.
This should work if you change the confirmed_bookings method to
self.confirmed_bookings
...
end
[EDIT] Missed the part about the current user, that function will be unavailable in the model, one solution is to pass it as a parameter to the confirmed bookings function:
class Booking < ActiveRecord::Base
def self.confirmed_bookings_for (user)
where(:status => 3, :accomodation_id => user.accomodation.id).order('updated_at DESC')
end
...
end
Then in your controller you can write
#bookings = Booking.confirmed_bookings_for current_user

Resources