Ruby on Rail, problemss with data save - ruby-on-rails

User has a page where he can edit his personal information(name, date of birth,...).
But the user can delete the value from the Name field, and then go to some link with the sidebar, and the information is saved as an empty string('').
Question: how do I make such a check, the user can not go to anywhere, if at least one field is empty? Or if it is empty, then fill it with the previous data?
Controller:
def editMain
unless #user = User.find_by_id(session[:user_id])
render_404
return
end
#user.update_attributes(params[:user])
if params[:user][:first_name] == ""
#user.errors.add(:first_name, "ERROR!")
end
if params[:user][:last_name] == ""
#user.errors.add(:last_name, "ERROR!")
end
if params[:user][:birtday] == ""
#user.errors.add(:birthday, "ERROR!")
end
#countries = Country.all
#cities = City.all #where(country_id: #user.city.country.id)
if #user.errors.empty?
flash[:notice] = "Succssesful!"
else
render "editMain"
end
end
Please, help me!

Use what framework gives you. You don't have to rewrite existing things.
This code =>
unless #user = User.find_by_id(session[:user_id])
render_404
return
end
Equals to =>
#user = User.find(session[:user_id])
Dont try to validate objects in controller.
This code =>
#user.update_attributes(params[:user])
if params[:user][:first_name] == ""
#user.errors.add(:first_name, "ERROR!")
end
if params[:user][:last_name] == ""
#user.errors.add(:last_name, "ERROR!")
end
if params[:user][:birtday] == ""
#user.errors.add(:birthday, "ERROR!")
end
Equals to this =>
if #user.update_attributes(params[:user])
flash[:notice] = "Succesful!"
redirect_to some_path
else
render "editMain"
end
and use activerecord validations.
models/user.rb
validates_presence_of :first_name, :last_name, :birthday

Frankly, this piece of code is quite a mess:
1) 404 is returned when you can't find a page (vs you can't find a resource (user) with some id)
2) 404 is returned automatically by rails, if page (route) isn't found
3) Model should do validation (vs controller)
4) Rendering could be done for your automatically in most cases
I would really recommend to read through Getting tarted with Rails:
http://guides.rubyonrails.org/getting_started.html

Related

Redirect_to in model after error

I have a function in my model to import data from a CSV file and I'd like to have validations should there be any errors. For example, when I upload the file, I search for a User based on an ID in the file. If there is no User with that ID, I'd like to redirect_to a different page with an error.
def self.getUser(scale_id)
#user = User.find_by(scale_id: scale_id)
if #user == nil
redirect_to users_path
else
return #user
end
end
def self.bulk_upload_weigh_ins(file)
output = []
errors = []
CSV.foreach(file.path, headers: true, ) do |row|
row = row.to_hash
#scale_id = row["scale_id"]
#user = getUser(#scale_id)
row.merge!(user_id: #user_id)
WeighIn.create! row.to_hash
end
end
...and no matter what path I put there, I get the following: undefined local variable or method 'users_path' for #<Class:0x007fa06f466998> even when it is a valid path.
Is there something wrong with redirecting like this? If yes, how should I do it?
The cleanest way for custom validations is to do something like:
In your model:
User < ActiveRecord::Base
validate :get_user
def initialize(params={})
self.id = params[:id]
end
def get_user
#user = User.find_by(self.id)
if #user.nil?
errors.add(:base, "Invalid User")
return false
else
return #user
end
end
In your controller you'd then do:
def whatever_action_youre_using
#user = User.new(user_params)
unless #user.valid?
redirect_to users_path
end
end
def user_params
params.require(:user).permit(:first_name, :email, :etc, :whatever_your_atts)
end
I have a function in my model that is searching for a User based on an
ID
undefined local variable or method 'users_path' for
Class:0x007fa06f466998
As #Dave Newton mentioned in the comments, the path helpers are not available in models unless you specifically include them. Also you can't and never need to use redirect_to in a model. The application logic should be moved to the controller. Something like below should work
def get_user
#user = User.find_by(scale_id: params[:user][:scale_id])
if #user.nil?
redirect_to users_path, notice: 'Your error message here'
else
return #user
end
end

Rails Create article with other user not current_user

I would like to create an article with other user not current_user and for that I'm saving in a session the id to the other user and I recover this id with a collection in the view to this point everything work fine but when I'm trying to use my helper :selected_user into my articles controller with a if sentence doesn't work here is my code:
def new
if selected_user.present?
#article = selected_user.articles.build state: :step1
render_wizard
else
#article = current_user.articles.build state: :step1
render_wizard
end
end
so, I'm asking if the selected_user.present? I would like to create the article with this user_id but else I would like to create it with the current_user
my create method is:
def create
if selected_user.present?
step = :step1
#article = selected_user.articles.build article_params_step1
#article.state = step.to_s
if #article.save
redirect_to wizard_path(next_step, article_id: #article)
else
render_wizard
end
else
step = :step1
#article = current_user.articles.build article_params_step1
#article.state = step.to_s
if #article.save
redirect_to wizard_path(next_step, article_id: #article)
else
render_wizard
end
end
end
so, yeah when I run my view the controller jump to the else section.
just for clarify my selected_user not return nil but here is the implementation:
selections_controller.rb:
class SelectionsController < ApplicationController
before_action :authenticate_user!
def create
session[:selected_user_id] = params[:user][ :user_id]
redirect_to root_path
end
end
and in my application_controller.rb:
helper_method :selected_user
def selected_user
#selected_user ||= User.find(session[:selected_user_id])
end
and in the view:
<%= form_tag( { :controller => "selections", :action => "create" } , :class => "navbar-form navbar-left") do %>
<%= collection_select(:user, :user_id, User.all, :id, :name, prompt: "Escoge cliente")%>
<%= submit_tag 'Enviar' %>
<% end %>
if I try create an article without select an user from my collection appear this error:
Couldn't find User with 'id'=
but when I select my user from the collection everything works fine. so just I want when I don't select nothing create with the current_user.
Thanks for your time !
Regards !
The reason why you were seeing the error
Couldn't find User with 'id'=
when you haven't selected a user was that the session[:selected_user_id] was nil and your old selected_user with following code was throwing the error.
def selected_user
#selected_user ||= User.find(session[:selected_user_id])
end
User.find method expects either a single id or an array of ids. If you give a single id and if it finds the relevant record in the database then it will returns that instance. If you give an array of ids and if it finds those relevant records in the database, then it will return array of those instances. But if you pass nil to it, then it will through the error Couldn't find User with 'id'= as it won't find a relevant record.
But your updated selected_user implementation:
def selected_user
#selected_user ||= session[:selected_user_id] && User.find_by_id(session[:selected_user_id])
end
is working because, first you are checking for the existence of session[:selected_user_id] value and second you are using User.find_by_id instead of User.find.
User.find_by_id either returns a single instance of the record if it finds it in the database or will return nil if it doesn't find the record. It will never through an error.
Refer to ActiveRecord#find and ActiveRecord#find_by for more info.
I'm not sure why is working and what is the different but my solution for the problem it was to add this to my selected_user method:
def selected_user
#selected_user ||= session[:selected_user_id] && User.find_by_id(session[:selected_user_id])
end
and with that I don't have the nil error and entry to the if statement without errors.

Begin/End/While in Ruby

What is the best approach to do this?
#user = UserFinder.find(first_name: "Dave")
if !#user
return render :text => '// No user named Dave found'
end
if #user.last_name != "Edwards"
# This is not the guy we want
#user.destroy
# Now we want to re-execute the search again forever until we find Dave Edwards
end
Keep in mind this is an incredibly simplistic example of the real code I have, so I know that I can just filter the query in the first place, but I want it this way.
I'm not sure how to do this with begin and while blocks because destroying an unwanted user is required if we run into one.
My Attempts:
begin
#user = UserFinder.find(first_name: "Dave")
return render :text => '// No user named Dave found'
end while #user.destroy if #user.last_name != "Edwards"
This doesn't work because the while block gets the result of destroy not the comparison of last_name
P.S. Just if this is impossible this way, How to reload the Rails controller action with the same params?
Try this:
begin
#user = UserFinder.find_by_first_name("Dave")
render :text => '// No user named Dave found' if #user.nil?
return if #user.nil?
end while #user.last_name != "Edwards" && #user.destroy
Does this work for you?
while (#user = UserFinder.find(first_name: "Dave")) and #user.last_name != "Edwards"
#user.destroy
end
if #user
render ...
else
render :text => '// No user named Dave found'
end
user.destroy while user = UserFinder.where(:first_name => "Dave", :last_name => "Edwards").first

Rails: how can I optimize this action

The action bellow creates a new comment.
A user has many statuses
A status has many comments
How can optimize this action so that head 401 and return is not repeated many times.
def create
#user = User.where(id: params[:user_id]).first
if #user
if current_user.friend_with?(#user) or current_user == #user
#status = #user.statuses.where(id: params[:status_id]).first
if #status
#comment = #status.comments.build(params[:comment])
#comment.owner = current_user
if #comment.valid?
#comment.save
current_user.create_activity(:comment_status, #comment, #user)
else
head 401 and return
end
else
head 401 and return
end
else
head 401 and return
end
else
head 401 and return
end
end
Thank you.
When do you want to return 401?
when a user has not been found
when a user is not a current user or is not a friend of that user
when a status has not been found
when new comment has not been successfully created
Instead of using so many conditionals, you can use methods that raise exceptions. When you do so, you can rescue from that exceptions with the desired behavior (rendering 401).
So my suggestions for listed conditions are:
use find! instead of where and then first.
raise something, preferably custom exception (NotAFriendError)
same as 1., use find!
use create!, it's an equivalent to new and then save! which will raise ActiveRecord::RecordInvalid exception if it fails on validation.
Here's the result:
def create
begin
#user = User.find!(params[:user_id])
raise unless current_user.friend_with?(#user) || current_user == #user
#status = #user.statuses.find!(params[:status_id])
#comment = #status.comments.
create!(params[:comment].merge(:owner => current_user))
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
head 401
end
# everything went well, no exceptions were raised
current_user.create_activity(:comment_status, #comment, #user)
end
You have a lot of excessive checking and branching in your code, so it can be simplified to this:
def create
success = false
#user = User.find(params[:user_id])
current_user_is_friend = current_user.friend_with?(#user) || current_user == #user
if #user && current_user_is_friend && #status = #user.statuses.find(params[:status_id])
#comment = #status.comments.build(params[:comment])
#comment.owner = current_user
if #comment.save
current_user.create_activity(:comment_status, #comment, #user)
success = true
end
end
render(status: 401, content: '') unless success
end
A few things I did:
Combine a lot of the if conditions, since there was no need for them to be separate.
Change where(id: ...).first to find(...) since they're the same. Note that, if the find fails, it will give a 404. This may make more sense, though (I think it does)
Don't call #comment.valid? right before #comment.save, since save returns false if the object wasn't valid.
Use || instead of or for boolean logic (they're not the same).
Use render(status: ..., content: '') instead of head ... and return.
Use a boolean variable to track the success of the method.
I would advise that you try and pull some of this logic out into models. For example, User#friend_with should probably just return true if it's passed the same User.
def create
#user = User.where(id: params[:user_id]).first
if #user
if current_user.friend_with?(#user) or current_user == #user
#status = #user.statuses.where(id: params[:status_id]).first
if #status
#comment = #status.comments.build(params[:comment])
#comment.owner = current_user
if #comment.valid?
#comment.save
current_user.create_activity(:comment_status, #comment, #user)
everythingOK = true
end
end
end
end
head 401 and return unless everythingOK
end

how do I make changes to inpus from edit/update in Rails?

I have he following code in my update action for a Controller. The code works when in the create, but doesn't seem to kick in under update:
def update
#contact = Contact.find(params[:id])
# bug, why isn't this working?
unless #contact.fax.empty?
#contact.fax = "1" + Phony.normalize(#contact.fax)
end
unless #contact.phone.empty?
#contact.phone = "1" + Phony.normalize(#contact.phone)
end
if #contact.update_attributes(params[:contact])
flash[:notice] = "Successfully updated contact."
redirect_to #contact
else
render :action => 'edit'
end
end
these should be in your model. FAT model, SKINNY controller:
# contact.rb
...
# may need require 'phony' and include Phony
before_save :prep
def prep
self.fax = 1+Phony.normalize(self.fax) unless self.fax.empty? || (self.fax.length == 11 && self.fax[0] == 1)
self.phone = 1+Phony.normalize(self.phone) unless self.phone.empty? || (self.phone.length == 11 && self.phone[0] == 1)
end
...
Edit:
As I mentioned in my comment, it's better in terms of storage and efficiency and indexing to store as a bigint unsigned in your database and add the prettiness to the numbers in a method. This way, your site is always normalized (no two phone numbers will ever look different because they are formatted 'on the fly').
# sample methods
def phony
str = self.phone.to_s
"#{str[0..2]}-#{str[3..5]}-#{str[6..10]}"
end
# use a similar method for faxing, but I'll write
# this one differently just to show flexibility
def faxy
str = self.fax.to_s
"+1 (#{str[0..2]}) #{str[3..5]}-#{str[6..10]}"
end
you never call save on #contact in your unless blocks, so your call to #contact.update_attributes(params[:contact]) undoes any change you made in those blocks (because those keys in the params hash correspond to empty values).
def update
#contact = Contact.find(params[:id])
if #contact.update_attributes(params[:contact])
#contact.update_attributes(:fax => "1" + Phony.normalize(#contact.fax)) unless #contact.fax.empty?
#contact.update_attributes(:phone => "1" + Phony.normalize(#contact.phone)) unless #contact.phone.empty?
flash[:notice] = "Successfully updated contact."
redirect_to #contact
else
render :action => 'edit'
end
end
You could use update_attribute but that bypasses validation.
You could also use a before_save callback in the Contact class, but you would have to check if the phone or fax are already "normalized."

Resources