updating user review in Ruby on Rails - ruby-on-rails

Hi i was wondering if there was a way a user can update a review they have already written, i tried using cancan but ran into a few problems so i rather find out if there is an easier way. This is code from the 'new' method in the reviews controller
def new
if logged_in?
#review = Review.new(:film_id => params[:id], :name =>
User.find(session[:user_id]).name)
session[:return_to] = nil
else
session[:return_to] = request.url
redirect_to login_path, alert: "You must be logged in to write a review"
end
end
and the 'create' method
def create
# use the class method 'new' with the parameter 'review', populated
# with values from a form
#review = Review.new(params[:review])
# attempt to save to the database, the new review instance variable
if #review.save
# use the class method 'find' with the id of the product of the
# saved review and assign this product object to the variable 'product'
film = Film.find(#review.film.id)
# redirect the reviewer to the show page of the product they reviewed,
# using the product variable, and send a notice indicating the review
# was successfully added
redirect_to film, notice: "Your review was successfully added"
else
# if the review could not be saved, return / render the new form
render action: "new"
end
end
i want the user to edit their review if they have already written a review for a product. Instead of having two reviews from the same user for the same product.

You could potentially sub something like this into the create method:
# Assumes that your user names are unique
#review = Review.find_or_create_by_film_id_and_name(params[:review][:film_id], User.find(session[:user_id]).name)
#review.update_attributes(params[:review])
This does the following
Checks whether the user has created a review for the film
If yes, assigns the existing review to the #review instance variable
If not, creates a new Review object and assigns it to #review
Updates #review with params[:review]
Alternatively, the following statements will accomplish the same without using the Rails find_or_create convenience method:
user_name = User.find(session[:user_id]).name # To avoid two DB lookups below
#review = Review.find_by_film_id_and_name(params[:review][:film_id], user_name) || Review.new(:film_id => params[:review][:film_id], :name => user_name)
#review.update_attributes(params[:review])

To update a record, you should use the update action, which is requested after the user submitted the edit form.

Make your User model have has_many/has_one :reviews. And Review model belongs_to :user. And then if you have any kind of authorization(and you should have, for ex: devise) you'll know if user of review is currently logged user. If so then render edit button, otherwise not render.
Also according to CRUD conventions, there are 2 actions you need. First its edit and other one update. You can read about it on railsguides.com

Related

Rails: first_or_create not saving

My goal for my application is to only show a form page with existing data or a blank form if new. I've accomplished this by using a callback that created a blank record when the user is created.
User model:
before_create :build_health_profile
However, if for whatever reason a users "health_profile" were to be destroyed or non-existant, it breaks my entire app with:
"undefined method `health_profile' for nil:NilClass"
It was mentioned to me that the "first_or_create" method could solve this by show a new form or finding the existing one, but I can't get it to save the fields. It directs to my root with my save alert like it saved, but nothing gets actually saved.
Controller:
class HealthProfilesController < ApplicationController
def new
#health_profile = current_user.build_health_profile
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end
private
def health_profile_params
params.require(:health_profile).permit(
:age,
:weight,
:height,
:gender
)
end
end
I've seen where I could use a block for "first_or_create", but no luck getting that to work.
View:
<%= link_to "Health Profile", new_health_profile_path %>
Models:
class User < ActiveRecord::Base
has_one :health_profile, dependent: :destroy
end
class HealthProfile < ActiveRecord::Base
belongs_to :user
end
If you use first_or_create then that calls the save method as part of it on the record and tries to save that in the database. If it can't save the record, then the transaction is rolled back. So, you want to use: first_or_initialize here which is like new and does not save the record in the database immediately. It just loads the data. So, you can call save on it in the next line of your code.
So, in your code, where you have:
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
Here you are not controlling the save part, that's already being done by the first_or_create method.
So, you actually want to just load the object (NOT save yet) by using first_or_initialize:
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
and then, in the next line, you can call the save and based on it's return value you can take the decision:
if #health_profile.save
# do stuff if successfully saved health_profile
else
# otherwise
render 'new'
end
Because you have #health_profile.save,
You should change first_or_create into first_or_initialize
first_or_create immediately trigger save, whereas first_or_initialize would just assign the values to a New record or to an already existing record if record exists already
I was able to fix the problem of the record resetting itself when going back to the form by adjusting the new action. Thats everyone for the help.
def new
#health_profile = current_user.health_profile || HealthProfile.new
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end

Get any id to any user exists in the database

I am new to rails and have a task that asks me to send an invitation for any user to be admin in my magazine here is my piece of code
def invite
inviteUser = { 'user_id' => current_user.id, 'Magazine_id' => params[:id] }
CollaborationInvitation.create(inviteUser)
#magazine = Magazine.find(params[:id])
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
I need to replace current_user.id with something that refers to any user's id which exists in my database to send him an invitation to be admin with me I tried to add #User=Users.All and then pass it as a variable but it got me an error I tried a lot of things but every time I get an error except for adding current_user.id
ps: I am using devise for authentication
You asked a couple things, and it is kind of confusing what you want to do.
Here is how you get all ids of records in a model.
Rails4: User.ids
Rails3: User.all.map(&:id)
Or (not sure if #pluck is in Rails 3 or not)
User.pluck(:id)
If you want to get a random user (you mentioned "any user") you could do.
User.find(User.pluck(:id).sample)
Though I think what you really want to do is to pass the id or some other attribute of a user as a param to the action and send that user an invitation.
Presumably you either have a post or get route for "users#invite" (the action you wrote in your question). You can add a named parameter there or you can pass a url param or if you are using a post route, you could add the param to the post body.
Then in your contoller you can do something like this (I'll use email as an attribute):
def invite
#user = User.find_by(email: params[:user_email])
#Rails 3 like this
# #user = User.find_by_email(params[:user_email])
# now do stuff with user
end
User.all will return you the collection of users. So,
Find the user object to get an id...
Try this code....
def invite
inviteUser = { 'user_id' => User.find_by_email('user#example.com').id, 'Magazine_id' => params[:id] }
CollaborationInvitation.create(inviteUser)
#magazine = Magazine.find(params[:id])
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
You can try
User.last.id
or
User.find_by_email("xyz#test.com").id
or
User.where(email: "xyz#test.com").first.id
Replace xyz#test.com with desired user email address. To get more details on rails active record query interface, please read rails guides http://guides.rubyonrails.org/active_record_querying.html

Rails4 - Edit parameters of an Object before remotely saving it in the database

Beginner with some dev experience here.
I have an app with multiple models and I have managed to work everything out but i am stuck here.
I have a model, called CartEntries
class CartEntry < ActiveRecord::Base
acts_as_paranoid
belongs_to :cart
belongs_to :sign
With a create method in the Cart Entry controller
def create
#entry = #cart.entries.create(entry_params)
if #entry.save
flash[:notice] = translate 'flash.notice'
else
flash[:error] = translate 'flash.error'
end
support_ajax_flashes!
respond_to do |format|
format.html # renders view
format.json { render json: #entry }
end
end
And a Model Sign with static signs inputed in the database and no create method.
class Sign < ActiveRecord::Base
has_many :cart_entries
accepts_nested_attributes_for :cart_entries
And from a Sign's view I initialize a new instance of a CartEntry and succsessfuly create a new Cart Entry after clicking the link, generating a notification.
<% #entry = CartEntry.new(sign: #sign)%>
<%= link_to t('.add_to_cart'), user_cart_entries_path(:entry => #entry.attributes),method: :post, remote: true, "data-type" => :json%>
The Cart Entry has another field called Count with a default value of 1. Im looking for a way for the user to input this number in a text field and when creating the Cart Entry , pass the Count the user inputed instead of the default 1.
What ever I try passes the default value.
While
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
Does the trick properly, and passes 5 as the value , but I want the user to input this number since its clearly a variable.
While I understand that
<% #entry = CartEntry.new(sign: #sign)%>
Initializes the entry object on page load and that i must move it, I'm asking you kind people, where?
UPDATE
Entry Params:
private
def entry_params
params.require(:entry).permit(:sign_id, :count)
end
Answering in reverse order from your question:
Initializing the new CartEntry object should probably be in the new action of your controller. Rails controllers often have both new and create, new being tied to rendering the form to receive input and create being the action tied to the 'Submit' button. Your new action is often just something like:
def new
#entry = CartEntry.new(sign: #sign)
end
Your view to prompt the user for data should be named new.html.erb and have the form in it.
For getting the data from the form to your create method, you are half way there I think. If you moved the example you gave:
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
to the create action in the controller, it would be
#entry = CartEntry.new(sign: #sign, count: params[:count])
#entry.save
Remember 'params' is just a hash that contains the form input data.
Hope that helps!

Is there a way to get the visitors city and country in Rails controller?

In my current app, i use Geocoder gem to get the city and the country of the visitor. I use hidden fields in my view to get these details. When the login form is submitted, these details will be sent to the controller and the controller will save them to the database. When I try to get these details directly from the controller by using
request.location.city
It will assigning a blank value to the database. If I use hidden fields in the view, some one can temper with them right? So, how can I fix this?
You should store visitor information before you render any content:
class UsersController
def new
# I suspect that, for fast insert, you should probably use a NoSQL database
# to perform `store!` or even just write it to a log file
Visitor.store!(:city => request.location.city, :ip => request.ip)
end
def create
#user = User.build(params[:user].merge(:city => request.location.city))
if #user.valid?
#user.save
flash[:notice] = "You've been registered!"
redirect_to user_dashboard_path
else
flash[:notice] = "Couldn't register your account"
render action: "new"
end
end
end

Rails - Add Record to join table from controller

I'm trying to create a record within a join table from the action of a button. I would have an events model and would like to track selected events from each user.
I used the HABTM relationship since I don't really need any extra fields.
User.rb:
has_to_and_belongs_to_many :events
Event.rb:
has_to_and_belongs_to_many :users
Events_Users Migration:
[user_id, event_id, id=>false]
I'm getting stuck on the actual creation of the record. Someone helped me earlier with adding the record in within the console:
u = User.find(1)
u.events << Event.find(1)
Now I would like to perform the action as a result of clicking a link... Is this in the right direction?
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
if #user.events.save(params[:user][:event])
flash[:notice] = 'Event was saved.'
end
end
Should I add a #user.events.new somewhere and if so where do I put the params of which user and which event?
The following code should work (assuming that you pass in an parameter with the name id that corresponds to the id of an event object):
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The problems I see in your code are:
You are passing a hash to .save. Save should only take a boolean value corresponding whether validations should be run and is true by default. However .create and .new can accept a hash of values. (.save would be used after .new).
You load an event through params[:id] but then you attempt to create an event through params[:user][:event]. Which do you want to do? Create or load? (my example assumes load)
Actions that have an effect such as this one should happen when a user clicks a button and submits a form rather than 'clicking a link'. This code may be vulnerable to cross site request forgery (Someone could trick someone into clicking a link on another site that ran this action). Rails forms, if correctly implemented, are protected against this because they use a request forgery protection token.
Most likely you want to redirect the user after this action. Rendering pages after executing actions like this (rather than redirecting) is considered bad practice.
What you did in the console you need to do in the controller.
def add
#user = User.find(session[:user_id])
#event = Event.find(params[:id])
#user.events << #event
flash[:notice] = 'Event was saved.'
end
The thing to note here is that the << operator for existing records will cause the association to be persisted immediately.
Take a look at the ActiveRecord documentation for more info.
If the event_id is passed as params[:id] and you are adding only one event in this call then, you can do the following in your controller code:
User.find(session[:user_id]).events << Event.find(params[:id])
flash[:notice] = 'Event was saved.'
You don't need explicit save to save the has_many association of an existing model instance.
Scenario 1
u = User.new(..)
u.events << Event.first
# Now you need to call `save` in order to save the user object
# and the events association
u.save
Scenario 2
u = User.first
u.events << Event.first
# Don't need to call `save` on `u` OR `u.events`

Resources