Rate a post only once per user in rails - ruby-on-rails

I have trouble in allowing users to rate a post. My task is to enable the user to rate a post only once. On the show page, the post I have includes radio buttons for rating. If the user tries to rate for the second time it needs to update the previous rating done by the user for the same post. The issue I am facing is that user is able to rate a post multiple times. How to resolve this?
User model:
class User < ApplicationRecord
has_many :posts
has_many :ratings
end
Post model:
class Post < ApplicationRecord
has_many :ratings
belongs_to :user
end
Ratings model
class Rating < ApplicationRecord
belongs_to :post
belongs_to :user
end
In the post controllers i have used nested attributues for ratings.
def show
#post = #topic.posts.find(params[:id])
#rate = #post.ratings.all
#rate = Rating.where(post_id: #post.id).group("rate").count
end
private def post_params
params.require(:post).permit(:title, :body, ratings_attributes: [:rate])
end
The show page of post include the creating of rating using <fieldset>:
<%= form_for [#topic, #post] do |f| %>
<%= f.fields_for :ratings, #post.ratings.build do |builder| %>
<fieldset>
<% for i in 1..5 %>
<%= builder.radio_button :rate, i %><%= i %>
<% end %>
</fieldset>
<% end %>
<%=f.submit "Rate" %>
<% end %>

First, add validation to the Rating to enforce uniqueness on the combination of user and post. This will stop a duplicate rating ever being created.
validates_uniqueness_of :post_id, scope: :user_id
Then, in the action that saves the rating, first check if there is a record that can be updated, else create a new one.
#rating = Rating.find_or_initialize_by(user: #user, post: #post)
#rating.rate = params[:rate]
#rating.save
This might not be perfect syntax, but you should get the idea about what you are trying to do and can adjust to match your code.

You can use first or initialize like this
#rating=Rating.where(post_id: #post.id,user_id: current_user).first_or_initialize

Related

Associate many records to one with has_many_through

I'm trying to associate several dinners to a meal with a has_many: through relationship when the user hits "save". My question is not with the mechanics of has_many: through. I know how to set that up and I have it working in the Rails console, but I just don't know how to set up the view to associate several records at once.
I have models set up like this:
class Dinner < ApplicationRecord
has_one :user
has_many :meals
has_many :meal_plans, through: :meals
end
class MealPlan < ApplicationRecord
belongs_to :user
has_many :meals
has_many :dinners, through: :meals
end
class Meal < ApplicationRecord
belongs_to :dinner
belongs_to :meal_plan
end
With a meal plan controller:
def create
#meal_plan = current_user.meal_plans.build(meal_plan_params)
respond_to do |format|
if #meal_plan.save
format.html { redirect_to root_path, notice: 'Dinner was successfully created.' }
end
end
end
private
def meal_plan_params
params.require(:meal_plan).permit(dinners: [])
end
My question is about the view, in the new view, I create a #meal_plan and I want to pass several different dinners into the meal plan. Below the value: #dinners is just 7 random dinners pulled from the Dinners table.
<%= form_with model: #meal_plan do |f| %>
<%= f.hidden_field(:dinners, value: #dinners)%>
<%= f.submit 'Save'%>
<% end %>
Again, I've gotten this to work by running something like `usr.meal_plans.create(dinners: [d1, d2])`` in the Rails console but I don't
You can use the form option helpers to generate selects or checkboxes:
<%= form_with model: #meal_plan do |f| %>
<%= f.collection_select :dinner_ids, Dinner.all, :id, :name, multiple: true %>
<%= f.collection_checkboxes :dinner_ids, Dinner.all, :id, :name %>
<%= f.submit 'Save'%>
<% end %>
_ids is a special setter / getter generated by ActiveRecord for has many assocations. You pass an array of ids and AR will take care of inserting/removing the join table rows (meals).
You also need to change the name in your params whitelist:
def meal_plan_params
params.require(:meal_plan).permit(dinner_ids: [])
end
If you want to to pass an array through hidden inputs you can do it like so:
<% #dinners.each do |dinner| >
<%= hidden_field_tag "meal_plans[dinner_ids][]", dinner.id %>
<% end %>
See Pass arrays & objects via querystring the Rack/Rails way for an explaination of how this works.

Rails 4 - Making profile pages sharable

SOLUTION:
I changed this line of code and now it displays perfectly.
<% if answer.user == current_user %>
to
<% if answer.user == #user %>
I posted a similar question last night but this is a new one.
I am building a question/answer app. The questions are static and provided, not user generated. The data is being accessed via a nested relationship.
On the individual user page, I am showing the questions/answers for that specific user using the code below. It is working great. Unfortunately, I want users to be able to share their profile pages and with the way things are set up, users can only see their own stuff, not that of others. What do I need to change so that only the data for user 1 is displayed on /users/1 but user 2 can view it?
I am assuming it will have something to do with using params to find the user vs. current_user, but I'm not sure how to set it up.
Here is the code in my show.view.
<% #user.questions.uniq.each do |question| %>
<h3><div class="answer"><%= question.question %> </div></h3>
<% question.answers.each do |answer| %>
<% if answer.user == current_user %>
<div class="answer"><%= answer.answer %> <%= answer.created_at.strftime("(%Y)") %></div>
<% end %>
<% end %>
<% end %>
My models:
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user
end
class Question < ActiveRecord::Base
has_many :answers
has_many :users, through: :answers
end
class User < ActiveRecord::Base
has_many :answers
has_many :questions, through: :answers
end
My Routes:
resources :users do
resources :answers
end
I'm happy to provide any other code that would be helpful. Thanks!
Okay, then you need to have in your show method in user controller something like this:
#answers_to_one_question = Answer.all.where("question_id = ?", question_id)
or
#answers_to_one_question = User.all.answers.where("question_id = ?", question_id)
And in the show view something like this:
<%= render partial: "answer", collection: #answers_to_one_question, as: answer %>
I didn't test this code, but the logic is like this.

Rails 4 has_many :through w/ check_box_tag - only one side of associate being saved to join table

I'm fairly new to Rails and I've been trying to extend Michael Hartl's tutorial in various ways. One of which is to model user interests using a has_many :through association. I have the following models set up:
class Interest < ActiveRecord::Base
has_many :user_interests, dependent: :destroy
has_many :users, through: :user_interests
end
class User < ActiveRecord::Base
has_many :user_interests, dependent: :destroy
has_many :interests, through: :user_interests
end
class UserInterest < ActiveRecord::Base
belongs_to :user
belongs_to :interest
end
My user_interests controller:
def index
end
def create
#user_interest = current_user.user_interests.build(params[:interest_ids])
if #user_interest.save
redirect_to current_user
flash[:success] = "Interests updated!"
else
render 'index'
end
end
def destroy
#user_interest = UserInterest.find(params[:user_id][:interest_ids])
current_user.user_interests(#user_interest).destroy
end
The view:
<%= form_for(current_user.user_interests.build(params[:interest_ids])) do |f| %>
<%= hidden_field_tag "user[interest_ids][]", nil %>
<% Interest.all.each do |interest| %>
<%= check_box_tag "user[interest_ids][]", interest.id, current_user.interest_ids.include?(interest.id), id: dom_id(interest) %>
<%= label_tag dom_id(interest), interest.activity %><br>
<% end %>
<%= f.submit "Update interests", class: "btn btn-large btn-primary" %>
<% end %>
When I run the app I can select a check box and click the submit button but only the user id is saved in the user_interests table. So it will look like:
id integer user_id interest_id created_at_timestamp updated_at_timestamp
1 2155 2014-04-06 ect... 2014-04-06 ect...
At first I was trying to use the users controller to create the association, but that was causing issues because I didn't have the interests check boxes displayed on the users#edit action, I wanted them to have their own page. What do I need to do to get the interest ids to save to the user_interests table along with the user id?
Please have a try with the following code.
def create
interests = Interest.where(id: params[:user][:interest_ids])
current_user.interests.push(interests)
flash[:success] = "Interests updated!"
redirect_to current_user
end

Insert & Update with strong parameters for nested `belongs_to` association

I have a form where a student can enroll in a class. When the user submits the form, he is enrolled in the class and his payment information is saved. In other words, an Enrollment object is created and the Student object is updated... except I cannot get the Student to update. Is this possible? If so, how?
My models...
class Student < ActiveRecord::Base
has_many :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :student
accepts_nested_attributes_for :student
end
My (abbreviated) form...
<%= form_for :enrollment, html: { id: "enrollment_form" } do |f| %>
<%= f.fields_for :student_attributes do |student_builder| %>
<%= student_builder.hidden_field :payment_name %>
<% end %>
<%= f.hidden_field :payment_token %>
<div class="field terms">
<%= f.check_box :agreed_to_terms %>
<%= f.label :agreed_to_terms, "I agree to the terms and conditions." %>
</div>
<% end %>
My controller...
class EnrollmentsController < ApplicationController
def create
#enrollment = Enrollment.new(enrollment_params)
#enrollment.clazz_id = #clazz.id
#enrollment.student_id = #student.id
#enrollment.save
end
private
def enrollment_params
params.require(:enrollment).permit(:payment_token, :agreed_to_terms, student_attributes: [:payment_name])
end
end
The POST parameters...
{
"enrollment"=> {
"student_attributes"=> {
"payment_name"=> "MasterCard ending in 9840"
},
"payment_token"=> "CC11ho86XxVqsUW7Cn9YjCHg?1376007969212",
"agreed_to_terms"=> "1"
},
"clazz_id"=> "7"
}
I've tried every permutation of student, students, _attributes in the form builder but none of them seem to work.
Ok, there are a few things I see:
Nested attributes, as described in the API, "allow you to save attributes on associated records through the parent." This means that
class Enrollment < ActiveRecord::Base
belongs_to :student
accepts_nested_attributes_for :student
end
inherently won't work because you're trying to accept nested atributes from the parent. So you need to start by rethinking your Active Record assications.
If we pretend that's all squared away, then more syntactical errors are:
Replace
<%= f.fields_for :student_attributes do |student_builder| %>
with
<%= f.fields_for :students do |student_builder| %>
This can be confusing, but passing :students to the fields_for helper calls the nested student object, whereas :student_attributes is the hash key from the POST parameters that fields_for produces.
In your strong parameters, you also need to permit the student :id so that your updating action has a reference. Otherwise, it will just create a new student. So change it to
private
def enrollment_params
params.require(:enrollment).permit(:payment_token, :agreed_to_terms, student_attributes: [:payment_name, :id])
end
I'm not sure if that's everything, but hopefully it's a start. Good luck.

Single form for parent and all children in relationship?

In my RoR I have a table games, stats, and players. Each game has many players, each player has many 'stats,' and each games has many stats through players. What i want to be able to do is in on my edit games form, i want there to be a a row of fields to add a new stat row, one per each players the game has. Ive been reading a lot about nested_attributes, but found new good resources how to fully do this.
UPDATE: Here's an updated set of classes based on the new associations you've stated in your comment
# models/game.rb
class Game < ActiveRecord::Base
has_many :teams
accepts_nested_attributes_for :teams
attr_accessible :name, :teams_attributes
end
# models/team.rb
class Team < ActiveRecord::Base
belongs_to :game
has_many :players
accepts_nested_attributes_for :players
attr_accessible :name, :players_attributes
end
# models/player.rb
class Player < ActiveRecord::Base
belongs_to :team
has_many :stats
accepts_nested_attributes_for :stats, reject_if: proc { |attributes| attributes['name'].blank? }
attr_accessible :name, :stats_attributes
end
# models/stat.rb
class Stat < ActiveRecord::Base
belongs_to :player
attr_accessible :name
end
# controllers/games_controller.rb
class GamesController < ApplicationController
def edit
#game = Game.find(params[:id])
#game.teams.each do |team|
team.players.each do |player|
player.stats.build
end
end
end
def update
#game = Game.find(params[:id])
if #game.update_attributes(params[:game])
render "show"
else
render text: "epic fail"
end
end
end
# games/edit.html.erb
<%= form_for #game do |f| %>
<%= f.fields_for :teams do |tf| %>
<p>Team: <%= tf.object.name %></p>
<%= tf.fields_for :players do |pf| %>
<p>Player: <%= pf.object.name %></p>
<%= pf.fields_for :stats do |sf| %>
<%= sf.text_field :name %>
<% end %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
Note this doesn't do any sort of ajax "add another stat" or anything fancy. It just sticks one extra blank field at the end for each player. If you needed more, you could build more blank stat objects in the GamesController#edit action or implement some fancy pants javascript. Hopefully that will get you close enough to be able to get your real data working.
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
without seeing more of your own code, I can't be specific, but basically, you loop over all the players and "build" the stats... game.players.each {|p| p.build_stat} and then in the form, loop over all the players again, and display the stats (maybe limiting to the new_record? ones?) Or perhaps do the build right in the form so it shows a blank entry.
I think I see a potential problem though, with your models... If the stats are a specific representation of a specific game, then your model as you describe it doen't link them - you'd need a game_id and player_id in each stat record. If that were the case, you would build all the stats in the controller method, and loop over them in the view.

Resources