I have an update form in Rails 3 for admin users that fails silently, despite having validations. It was working previously, but when I moved everything to a namespace, it no longer saves.
Here is the relevant code from my controller:
def update
#admin = Admin::Admin.find(params[:id])
respond_to do |format|
if #admin.update_attributes(params[:admin])
flash[:success] = "'#{#admin.name}' was successfully updated."
format.html { redirect_to admin_admins_path }
else
format.html { render action: "edit" }
end
end
end
And the model (unfinished, but previously working):
class Admin::Admin < ActiveRecord::Base
validates :name, :presence=>{:message=>"Name can't be blank"}
validates :email, :presence=>{:message=>"Email can't be blank"},
:length => {:minimum => 3, :maximum => 254, :message=>"Email must be between 3 and 254 characters"},
:uniqueness=>{:message=>"Email has already been registered"},
:format=>{:with=>/^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message=>"Email must be a valid email format"}
validates :password, :presence=>{:message=>"Password can't be blank"}
end
And the first part of the form partial:
<%= form_for(#admin) do |f| %>
Everything loads properly, but when I try to save, my validations are ignored and it redirects to the index page with a success message, but without saving the data. I have a feeling I'm missing something to do with namespaces, but I'm not completely sure what the problem is. Could it be looking for the model in the base model directory?
Did you inspect the params? I could imagine that params[:admin] does not contain the forms values anymore.
So, VirtuosiMedia and I stepped through it, and RoR adds an "admin_" to represent the Admin:: namespace, so we had to look for params[:admin_admin].
Related
I'm a programming/rails beginner, and have encountered a bug I cannot wrap my head around.
I'm using/learning about the "has_secure_password" method. When I try and create a user in my console with a mismatched password/confirm_password, the console returns false and the error is "Password confirmation doesn't match Password". But, when I try and do the same thing within the UI given the below code (+ a view), it saves just fine! Now, notice that in my "user_params" method, I accidentally forgot to permit ":password_confirmation" which is how I noticed this issue in the first place. With that ":password_confirmation" added, the view throws an error but that's not the point. Why even without this is the new User record being successfully created with a mismatched password and password confirmation, even though it doesn't save in the console?
Here is my User model:
class User < ActiveRecord::Base
has_secure_password
validates :name, presence: true
validates :email, presence: true, format: /\A\S+#\S+\z/, uniqueness: {case_sensitive: false}
validates :password, length: {minimum: 4, allow_blank: true}
end
And my User controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user, notice: "Thanks for signing up!"
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
end
This is happening because the password_confirmation attribute is optional. When it isn't supplied to the model that has_secure_password, the model simply accepts the password.
When your password confirmation attribute isn't whitelisted in your controller via user_params, it isn't being passed to the model at all, which is why mismatches appears not to throw an error. In truth the validation isn't taking place at all.
This works in your console because it creates a user without involving a controller or strong parameter whitelisting.
Two issues here. First is that I need to access a model's id before all of its attributes are defined. Meaning that this:
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data
end
throws an error unless I removed the second line, which is not a good thing to do. My second issue is that I don't want to render json until a model has both attributes. This is my controller:
def create
#search = Search.create( name: (params[:name]) )
Resque.enqueue(InstagramWorker, #search.id)
respond_to do |format|
if #search.save
format.json { render json: #search }
format.html { redirect_to root_path }
else
format.html { redirect_to root_path }
end
end
end
Should I write some logic in the model to check for name && color_data before saving? And is there a workaround for accessing an id without breaking validations?
You probably can use conditional validations, like
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data, if: :some_condition?
private
def some_condition?
# condition logic here
end
end
You can't do this.
By calling Resque.enqueue(InstagramWorker, #search.id) you're telling resque to do something, but not as part of this request. So this could complete now, it could complete in 2 hours from now.
If you need to ensure that this completes before the request has finished, take it out of Resque.
What you could do is only validate the color_data on update, rather than create. Presumably your resqueue job calls #search.save. So by adding
validates :color_data, presence: true, on: :update
But this wouldn't stop the json being rendered, you can't get past the fact that this is not part of the request without taking it out of resqueue.
I was wondering whether anybody felt kind enough to help me figure out why this isn't working.
I have a Model lets call it Task which belongs to a Project Model. I basically want each Task to have a unique name per project (Project1 could have a task called task1 and so could Project2 but both could only have one called task1) . This seems to be what the :scope option is for but it doesn't seem to be working for me.
The task model is a nested resource within project and as such I call the create action via project_tasks_path(#project). It works fine creating tasks and assigning them to projects but the scope of the uniqueness validation is not taking hold. If I create a task task1 in Project1 I can't create one with the same name in task 2.
This is my setup:
Task.rb
class Task < ActiveRecord::Base
belongs_to :project
validates :name, presence: true, uniqueness: {scope: :project_id}
tasks_controller.rb
def create
#project = Project.find_by(id: params[:project_id])
#task = Task.new(model_params)
#print task to stdout
puts "#task"
ap #task
respond_to do |format|
if #task.save
flash[:notice] = "Successfully created task"
format.js
else
# no flash as form handles errors
format.js { render action: 'new' }
format.html { render action: 'new' }
end
end
end
for some reason when I output the contents of the newly created task, I get the following
#<Task:0x007ff7c7c3b178> {
:id => nil,
:name => "test",
:project_id => nil,
:created_at => nil,
:updated_at => nil
}
It seems that because project_id hasn't been set at this point it's using 'nil' as the value.
What's the best way to get around this? would it just be a custom validator?
Edit 1
def model_params
params.require(:model).permit(:name, :project_id)
end
Right, having been playing around with this, it seems that the way to make this type of validation is pretty straight forward. All it requires is that the nested resource be built in relation to it's project, this forces the :parent_id to be passed through to the validation as expected.
In the case of this toy example, that means that the create action has to look something like:
#project = Project.find_by(id: params[:project_id])
#task = #project.tasks.build(model_params)
It should be noted that because of Rails not supporting generation of nested resources from the command line, the way that the scaffold generated controllers handle creation is by Model.new(model_params) and then saving, this doesn't seem to pick up the :parent_id in time for the validation and so will need changing as above (in terms of the parent).
I did successfully validate duplicated url straight in the model. The code below shows that the validation works well when the user creates a new bookmark.
validate :url_cannot_be_duplicated_per_user
def url_cannot_be_duplicated_per_user
current_user = User.find(self.user_id)
if current_user.bookmarks.any?{|b| b.url.eql? self.url }
errors.add(:url, "already added")
end
end
But the problem is the validation prevents editing a bookmark because when editing it bascially same bookmark, it will go through the model again and catch the duplication. So with that code the update action never happen.
Any idea how to fix it?
PS: I did put a block if else in the controller to check url first before submitting to model. The code goes messy although the validation worked pretty much correctly.
My controller
if duplicated? params[:bookmark][:url]
flash[:error] = 'This bookmark already added'
#bookmark = current_user.bookmarks.build(params[:bookmark])
render 'new'
else
You can validate uniqueness with scope:
class Bookmark < ActiveRecord::Base
validates :url, :uniqueness => {:scope => :user_id}
end
I'm a relative newbie at Rails, so forgive me if my question's got an obvious answer.
I'm trying to include a field in a Rails form which isn't in the model/controller or the migration associated with the view.
The form is a simple public contact form, and I can validate against most of the fields easily enough. Eg name, email etc.
The model is form_submission.rb
However, I have a field in the contact form - captcha - which isn't mirrored in the form_submissions db table, etc.
There is a separate table, model etc for captcha which is captcha_answer.rb (etc)
The attributes for captcha_answer in the migration are: answer and is_correct.
The table simple contains a list of answers to a predefined question, some of which are true and some which are false.
Eg, the captcha question might be:
Which is these is an animal?
With the options of: cat, dog, tree, rabbit .. in a select.
What I want to be able to do is to validate that:
a) The captcha field exists in the POST (return message of "no captcha given" if not)
b) The answer given has a value in captcha_answers.is_correct of true (return message of "you gave a wrong answer" if not)
The capcha_answers.answer is always unique, so I want to do the equivalent of a SQL query which gets the first record where captcha_answers.answer = and returns the value of captcha_answers.is_correct
Like I say, if the attribute was in form_submissions then I'd be able to validate it no problem, but I can't figure out how I can validate a field against something in another model.
Any ideas?
You can just add for example a hidden field and catch it in the controller:
in your form:
<%= hidden_field(:signup, :pass_confirm, :value => 'abcd') %>
then in the controller:
params[:signup]
There you can access a different model and validate the answer.
Action in the controller like:
def update
#company = Company.find(params[:id])
puts "extra field:"
puts params[:signup]
respond_to do |format|
if #company.update_attributes(params[:company])
format.html { redirect_to #company, :notice => 'Company was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #company.errors, :status => :unprocessable_entity }
end
end
end
Define accessors for the extra field and use usual ActiveRecord validations:
class MyModel < ActiveRecord::Base
attr_accessor :extra_field
validates :extra_field, :presence => true
validate :custom_validation_method
def custom_validation_method
errors.add :extra_field, :invalid unless extra_field == "correct"
end
end