Rails find and redirect or Create - ruby-on-rails

I'm trying to set up a Create action in my model where it first checks to see if there is already an object in the database based on the params that I have. If there is, I would like to redirect to the show page for that object. If not, I would like to create an object using the params. How would I do this and what's the best 'Rails way' to accomplish it? I tried find_or_create_by but just ended up with duplicate entries in my database, which is exactly what I'm trying to avoid.
Here is my create action so far:
def create
#book = Book.new(book_params)
if #book.save
redirect_to #book
else
# do something else
end
end
I have my params set up this way:
def book_params
params.require(:book).permit(:title, :author, :isbn, :description)
end
I also have a uniqueness validation on :isbn.
validates :isbn, presence: true, length: { is: 13 }, uniqueness: true, numericality: true
Thanks.

A before_filter that runs before your create that does a query and then redirects out.
class BooksController < ApplicationController
before_filter :check_for_existing_book, :only => [:create]
def create
# your existing create logic here
end
private
def check_for_existing_book
book = Book.where(:isbn => params[:book][:isbn]).first
if book
redirect_to(book) and return
end
end
end
You'll probably want to clean up the logic for inspecting params[:book][:isbn] and ensure you don't bomb out on invalid input, but this is the gist.

Related

ActiveModel::ForbiddenAttributesError rspec on request post [duplicate]

I have this model in Ruby but it throws a ActiveModel::ForbiddenAttributesError
class User < ActiveRecord::Base
attr_accessor :password
validates :username, :presence => true, :uniqueness => true, :length => {:in => 3..20}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, :uniqueness => true, format: { with: VALID_EMAIL_REGEX }
validates :password, :confirmation => true
validates_length_of :password, :in => 6..20, :on => :create
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password= BCrypt::Engine.hash_secret(password, salt)
end
end
def clear_password
self.password = nil
end
end
when I run this action
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "You Signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
end
render "new"
end
on ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux].
Can you please tell me how to get rid of this error or establish a proper user registration form?
I guess you are using Rails 4. If so, the needed parameters must be marked as required.
You might want to do it like this:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user).permit(:username, :email, :password, :salt, :encrypted_password)
end
end
For those using CanCan. People might be experiencing this if they use CanCan with Rails 4+. Try AntonTrapps's rather clean workaround solution here until CanCan gets updated:
In the ApplicationController:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
and in the resource controller (for example NoteController):
private
def note_params
params.require(:note).permit(:what, :ever)
end
Update:
Here's a continuation project for CanCan called CanCanCan, which looks promising:
CanCanCan
For those using CanCanCan:
You will get this error if CanCanCan cannot find the correct params method.
For the :create action, CanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
create_params
<model_name>_params such as article_params (this is
the default convention in rails for naming your param method)
resource_params (a generically named method you could specify in
each controller)
Additionally, load_and_authorize_resource can now take a param_method option to specify a custom method in the controller to run to sanitize input.
You can associate the param_method option with a symbol corresponding to the name of a method that will get called:
class ArticlesController < ApplicationController
load_and_authorize_resource param_method: :my_sanitizer
def create
if #article.save
# hurray
else
render :new
end
end
private
def my_sanitizer
params.require(:article).permit(:name)
end
end
source:
https://github.com/CanCanCommunity/cancancan#33-strong-parameters
There is an easier way to avoid the Strong Parameters at all, you just need to convert the parameters to a regular hash, as:
unlocked_params = ActiveSupport::HashWithIndifferentAccess.new(params)
model.create!(unlocked_params)
This defeats the purpose of strong parameters of course, but if you are in a situation like mine (I'm doing my own management of allowed params in another part of my system) this will get the job done.
If using ActiveAdmin don't forget that there is also a permit_params in the model register block:
ActiveAdmin.register Api::V1::Person do
permit_params :name, :address, :etc
end
These need to be set along with those in the controller:
def api_v1_person_params
params.require(:api_v1_person).permit(:name, :address, :etc)
end
Otherwise you will get the error:
ActiveModel::ForbiddenAttributesError
Alternatively you can use the Protected Attributes gem, however this defeats the purpose of requiring strong params. However if you're upgrading an older app, Protected Attributes does provide an easy pathway to upgrade until such time that you can refactor the attr_accessible to strong params.
One more reason is that you override permitted_params by a method. For example
def permitted_params
params.permit(:email, :password)
end
If you are on Rails 4 and you get this error, it could happen if you are using enum on the model if you've defined with symbols like this:
class User
enum preferred_phone: [:home_phone, :mobile_phone, :work_phone]
end
The form will pass say a radio selector as a string param. That's what happened in my case. The simple fix is to change enum to strings instead of symbols
enum preferred_phone: %w[home_phone mobile_phone work_phone]
# or more verbose
enum preferred_phone: ['home_phone', 'mobile_phone', 'work_phone']

How to implement strong parameters in Rails 4

I'm having trouble implementing strong parameters, receiving the Undefined Method attr_accessible error locally.
Could anyone explain what exactly I have done wrong here.
users_controller.rb:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to root_url, :notice => "Signed up!"
else
render "new"
end
end
def user_params
params.require(:user).permit(:username, :email, :password, :password_confirmation)
end
end
And in user.rb:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
has_secure_password
validates_presence_of :password, :on => :create
end
And perhaps a foolproof fix for this...I've tried a number of attempts but I just can't seem to get this right.
strong_params are usually done in the controller, not in the model. it's also described like this in the api. so, there's also no need for you to set attr_accesible. this way different controllers can also set different fields on a model, e.g. a backend users controller could be allowed to set an admin flag, while the users controller on the frontend is not allowed to do that.
so, your user_params method belongs in your UsersController, and the create and update action use user_params to filter out the params you don't allow to be set. e.g.:
#user = User.new(user_params)
Rails 4 uses strong params by default, and you don't need attr_accessible. Also in rails 4 you permit params in the controller instead of the model.
How is attr_accessible used in Rails 4?

How to fix controller error in Ruby on Rails and get Paperclip up and running

I have been trying for quite a while to get Paperclip up and running on my website and have followed the step-by-step process outlined on github multiple times and it still won't work. I really need to get this up and running as soon as possible. When I run the code through localhost, I get the message "undefined method `id' for nil:NilClass." This is located in line 20 (commented below). Why doesn't it appear as an error for the identical line under the owners method?
class SurveysController < ApplicationController
def new
#survey = Survey.new
end
def create
end
def survey_params
params.require(:survey).permit(:name, :email, :password)
end
def owners
#survey = Survey.new(survey_params)
#survey.user_id=current_user.id
#survey.save
end
def seeker
#survey = Survey.new
#survey.user_id=current_user.id # line with the error (line 20)
#survey.save
end
private
def survey_params
params.require(:survey).permit(:first_name, :last_name, :email, :looking_for, :moving_to, :gender, :coed, :age, :roommate_type, :housing_type, :roommates_estimate, :roommates_amount, :roommates_group, :roommates_names, :max_rent, :move_in, :move_out, :bedrooms, :amenities, :apartment_pet, :roommate_pet, :hometown, :school, :company, :terms, :avatar, :wake_up, :bedtime, :smoke, :smokeoften, :smokesocially, :smokequit, :drink, :drinkoften, :drinksocially, :drinkquit, :drugs, :drugsoften, :drugssocially, :drugsquit, :interest, :sexualactivity, :sexprivacy, :roommatesexprivacy, :overnight, :overnightoften, :roommateovernight, :realty, :availability, :rent, :address, :otherroom, :age_min, :age_max, :age_mode, :pad_photo, :user_status, :sociability, :tidiness, :question, :noise, :political, :religion, :user_id)
end
def idcheck
end
end
What am I doing wrong and how can I get Paperclip up and running? I would really appreciate any kind of assistance I can get with this because I have a pretty close deadline.
Where are you setting current_user?
Generally this is set via a "sign_in" method or some such, eg:
app/helpers/sessions_helper.rb
module SessionsHelper
def sign_in(user)
# "permanent" = expires: 20.years.from_now.utc
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
return unless cookies[:remember_token]
#current_user ||= User.find_by(remember_token: cookies[:remember_token])
end
end
Your specific methods likely vary, but that general pattern is often assumed to be in place in your project. Wherever you handle login flow, the current user should be set to some variable that you can refer to via your controllers. You need to make sure you're setting and using that variable.
Additionally, you should have a before_filter in the controller to ensure only logged-in users can access these actions.
do you also use Devise?
if yes,
please add
before_filter :authenticate_user!, only: [:seeker]
after
class SurveysController < ApplicationController
then Devise will ask you login before you do the seeker action.
If you don't use devise, would you mind post your Gemfile or let me know where did you get this current_suer

Carrierwave delete file from controller

The title says everything. I currently have a model concern with which I'm setting if a model can have attachments or not using include Attachable. So far so good.
Then, when I display the list of files attached to a particular model, I'm adding a link to delete it such as :
DELETE /posts/:post_id/attachments/:id(.:format) attachments#destroy
For that purpose, I created an AttachmentsController with a destroy method. So I have 2 problems here. The first, how can I delete a file from this controller using Carrierwave (for delete the file itself and the table record)?
Second, since my attachable behavior gonna be plug in several model :
DELETE /posts/:post_id/attachments/:id(.:format) attachments#destroy
DELETE /users/:user_id/attachments/:id(.:format) attachments#destroy
...
How can I do in my AttachmentsController to delete a file depending on the associated model dynamically?
class Attachment < ActiveRecord::Base
include Sluggable
belongs_to :attachable, polymorphic: true
mount_uploader :file, AttachmentUploader
validates :name, presence: true, if: :file?
validates :file, presence: true, if: :name?
end
class AttachmentsController < ApplicationController
before_action :authenticate_user!
def destroy
// Don't know how to remove that file
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
end
Hope I was clear.
Thanks
EDIT :
Ok I create a tweak on the params hash in order to get the associated object dynamically within AttachmentsController :
private
def get_attachable_model
params.each do |name, value|
if name =~ /(.+)_id$/
model = name.match(/([^\/.]*)_id$/)
return model[1].classify.constantize
end
end
nil
end
Ok I finally found a solution myself. Here is my destroy method from AttachmentsController :
def destroy
model, param = get_attachable_instance
model_attach = model.find_by slug: params[param.to_sym]
file = model_attach.attachments.find_by slug: params[:id]
file.destroy
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to root_path
end
Not sure if it's the best way to go, but it does works

ActiveModel::ForbiddenAttributesError when creating new user

I have this model in Ruby but it throws a ActiveModel::ForbiddenAttributesError
class User < ActiveRecord::Base
attr_accessor :password
validates :username, :presence => true, :uniqueness => true, :length => {:in => 3..20}
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, :uniqueness => true, format: { with: VALID_EMAIL_REGEX }
validates :password, :confirmation => true
validates_length_of :password, :in => 6..20, :on => :create
before_save :encrypt_password
after_save :clear_password
def encrypt_password
if password.present?
self.salt = BCrypt::Engine.generate_salt
self.encrypted_password= BCrypt::Engine.hash_secret(password, salt)
end
end
def clear_password
self.password = nil
end
end
when I run this action
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "You Signed up successfully"
flash[:color]= "valid"
else
flash[:notice] = "Form is invalid"
flash[:color]= "invalid"
end
render "new"
end
on ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux].
Can you please tell me how to get rid of this error or establish a proper user registration form?
I guess you are using Rails 4. If so, the needed parameters must be marked as required.
You might want to do it like this:
class UsersController < ApplicationController
def create
#user = User.new(user_params)
# ...
end
private
def user_params
params.require(:user).permit(:username, :email, :password, :salt, :encrypted_password)
end
end
For those using CanCan. People might be experiencing this if they use CanCan with Rails 4+. Try AntonTrapps's rather clean workaround solution here until CanCan gets updated:
In the ApplicationController:
before_filter do
resource = controller_name.singularize.to_sym
method = "#{resource}_params"
params[resource] &&= send(method) if respond_to?(method, true)
end
and in the resource controller (for example NoteController):
private
def note_params
params.require(:note).permit(:what, :ever)
end
Update:
Here's a continuation project for CanCan called CanCanCan, which looks promising:
CanCanCan
For those using CanCanCan:
You will get this error if CanCanCan cannot find the correct params method.
For the :create action, CanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
create_params
<model_name>_params such as article_params (this is
the default convention in rails for naming your param method)
resource_params (a generically named method you could specify in
each controller)
Additionally, load_and_authorize_resource can now take a param_method option to specify a custom method in the controller to run to sanitize input.
You can associate the param_method option with a symbol corresponding to the name of a method that will get called:
class ArticlesController < ApplicationController
load_and_authorize_resource param_method: :my_sanitizer
def create
if #article.save
# hurray
else
render :new
end
end
private
def my_sanitizer
params.require(:article).permit(:name)
end
end
source:
https://github.com/CanCanCommunity/cancancan#33-strong-parameters
There is an easier way to avoid the Strong Parameters at all, you just need to convert the parameters to a regular hash, as:
unlocked_params = ActiveSupport::HashWithIndifferentAccess.new(params)
model.create!(unlocked_params)
This defeats the purpose of strong parameters of course, but if you are in a situation like mine (I'm doing my own management of allowed params in another part of my system) this will get the job done.
If using ActiveAdmin don't forget that there is also a permit_params in the model register block:
ActiveAdmin.register Api::V1::Person do
permit_params :name, :address, :etc
end
These need to be set along with those in the controller:
def api_v1_person_params
params.require(:api_v1_person).permit(:name, :address, :etc)
end
Otherwise you will get the error:
ActiveModel::ForbiddenAttributesError
Alternatively you can use the Protected Attributes gem, however this defeats the purpose of requiring strong params. However if you're upgrading an older app, Protected Attributes does provide an easy pathway to upgrade until such time that you can refactor the attr_accessible to strong params.
One more reason is that you override permitted_params by a method. For example
def permitted_params
params.permit(:email, :password)
end
If you are on Rails 4 and you get this error, it could happen if you are using enum on the model if you've defined with symbols like this:
class User
enum preferred_phone: [:home_phone, :mobile_phone, :work_phone]
end
The form will pass say a radio selector as a string param. That's what happened in my case. The simple fix is to change enum to strings instead of symbols
enum preferred_phone: %w[home_phone mobile_phone work_phone]
# or more verbose
enum preferred_phone: ['home_phone', 'mobile_phone', 'work_phone']

Resources