I am working with polymorphic associations and having some trouble. My models are setup like so:
class User < ActiveRecord::Base
has_one :phone, :as => :callable, :dependent => :destroy
end
class Client < ActiveRecord::Base
has_one :phone, :as => :callable, :dependent => :destroy
end
class Phone < ActiveRecord::Base
belongs_to :callable, :polymorphic => true
end
In my Users Controller
def create
#user = User.new(params[:user])
if #user.save
#user.phone.create(:area_code => params[:user][:area_code], :phone => params[:user][:phone])
redirect_to #user, :notice => "Account created successfully!"
else
render 'new'
end
end
In the development log I see where the phone and user are being inserted correctly, but when I go to edit the user, the fields for phone in the form are blank. Here is my edit method:
def edit_employee
#user = User.find(params[:id])
#title = "Edit #{#user.name}"
end
My edit user form looks like this.
- form_for #user do |f|
- if #user.errors.any?
.error_messages
%h2 Please correct the following errors
%ul
- for message in #user.errors.full_messages
%li= message
%p
= f.label :name, "Name"
= f.text_field :name
%p
= f.label :email, "Email Address"
= f.text_field :email
%p
= f.label :phone, "Phone"
= f.text_field :area_code, :style => "width: 50px;"
= f.text_field :phone, :style => "width: 100px;"
= f.label :ext, "Ext."
= f.text_field :extension, :style => "width: 60px;"
%p
= f.label :password, "Password"
= f.password_field :password
%p
= f.label :password_confirmation, "Confirm Password"
= f.password_field :password_confirmation
%p.button= f.submit
I know I should be adding something to this edit method, perhaps
#phone = #user.phone
But that didn't work either. This is the first go round with polymorphic associations so any help and and pointers are much appreciated. I watched the Railscasts on this topic but it didn't seem to follow my underlying functionality. Once again, thanks in advance for any help and let me know if any more information is needed!
you should look into using fields_for and nested_attributes. http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Do you have attr_accessible set in the user.rb model? If not I would add it because that is a security issue.
Ok I added the
accepts_nested_attributes_for :phone
in the user model. I also added the fields for phone in the new user form like so
%p
- fields_for #user.phone do |phone|
= phone.label :phone, "Phone"
= phone.text_field :area_code, :style => "width: 50px;"
= phone.text_field :phone, :style => "width: 100px;"
= phone.label :ext, "Ext."
= phone.text_field :extension, :style => "width: 60px;"
but now I am getting the ActionView::Template::Error (undefined method `model_name' for NilClass:Class) exception.
And yes I have attr_accessible in my model. I just placed a very watered down version in here.
Related
I'm relatively new to Rails (using Rails 4), and am having a problem with validation for my user model. Even when the form is fully filled in with both the passwords, when I submit the code two errors print out:
{:password=>["can't be blank"], :password_confirmation=>["doesn't match Password"]}
I would like the user to be saved into the database, but these validation errors are preventing that from happening. What I would like to know is what I need to change in order to get rid of these errors.
I am printing out the params object and it looks like this (the authenticity token is omitted here):
params: {"utf8"=>"✓","authenticity_token"=>"[omitted]",
"user"=>{"username"=>"testuser1", "password"=>"test",
"password_confirmation"=>"test", "email_attributes"=>{"email"=>"d#d.com"},
"first_name"=>"test", "last_name"=>"user", "gender"=>"male", "city"=>"la",
"state"=>"ca", "country"=>"usa", "dob"=>"1980-11-20"},
"commit"=>"Create Account", "action"=>"create", "controller"=>"users"}
So it appears that the password and password_confirmation attributes are getting passed correctly. I am wondering if this may have to do with the virtual attribute password I have defined in the user model, but if that is the case I am still not quite sure how to solve this problem. Any help would be greatly appreciated. Let me know if I need to elaborate further.
Here is relevant code for reference:
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_email
end
def create
if #user = User.create(user_params)
logger.debug "#{#user.errors.messages}"
logger.debug "params: #{params}"
redirect_to :action => "new"
else
logger.debug "#{#user.errors.messages}"
logger.flush
redirect_to :action => "new"
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :first_name, :last_name, :gender, :dob, :city, :state, :country, :admin_level, email_attributes: [:email])
end
end
Model:
class User < ActiveRecord::Base
has_one :email
validates_presence_of :username, :email, :password
validates_confirmation_of :password, :on => :create
accepts_nested_attributes_for :email
def password_valid?(candidatePass)
candidatePassAndSalt = "#{candidatePass}#{self.salt}"
candidatePasswordDigest = Digest::SHA1.hexdigest(candidatePassAndSalt)
if (candidatePasswordDigest == self.password_digest)
return true
else
return false
end
end
def password
end
def password=(text)
self.salt = Random.new.rand
passAndSalt = "#{text}#{self.salt}"
self.password_digest = Digest::SHA1.hexdigest(passAndSalt)
end
end
View:
<%= form_for #user, url: {action: "create"}, html: {class: "user-creation-form"} do |f| %>
<%= f.text_field :username %>username<br/>
<%= f.password_field :password %>pw<br/>
<%= f.password_field :password_confirmation %>pwcopy<br/>
<%= f.fields_for :email do |email_form| %>
<%= email_form.text_field :email %>email<br />
<% end %>
<%= f.text_field :first_name %>first<br/>
<%= f.text_field :last_name %>last<br/>
<%= f.radio_button :gender, "male" %>
<%= f.label :gender_male, "M" %>
<%= f.radio_button :gender, "female" %>
<%= f.label :gender_female, "F" %><br />
<%= f.text_field :city %>city<br/>
<%= f.text_field :state %>state<br/>
<%= f.text_field :country %>country<br/>
<%= f.date_field :dob %>dob<br/>
<%= f.submit "Create Account" %><br/>
<% end %>
The issue is your empty getter:
def password
end
It always return nil.
2 small additions to the previous answer, which should resolve your issue by the way.
1) If you're using Rails >3 (I assume you are by looking at your user_params method in the controller) you don't have to specify all those password fields and validations.
ActiveRecord automatically includes this ActiveModel method :
has_secure_password
More details at : http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
2) If the uncrypted password/password_confirmation are shown in your log files your app is insecure. Add this to your config/application.rb :
config.filter_parameters = [:password, :password_confirmation]
This should not be needed if you are using has_secure_password in your User model.
I have a User Model and an Instructor Model. There is a one-to-one relationship between user and instructor. And some users will be instructors and some will not. As such I have a registration form that uses a fields_for method to write to both.
How can I write to the instructor table only on the condition that they say they are an instructor, such as through a checkbox. And when they do write I want to maintain my validations of the table along with the rest of the form
Ideally this would work best if I can do this through the model, but I'm open to all suggestions.
Instructor Model
class Instructor < ActiveRecord::Base
belongs_to :user
validates_presence_of :school_url, :etc...
attr_accessible :school_url, :etc...
end
User Model
class User < ActiveRecord::Base
has_one :instructor, :dependent => :destroy
validates_uniqueness_of :email
validates :email, :confirmation => true
accepts_nested_attributes_for :instructor
attr_accessible :email, :password, :instructor_attributes, :etc
end
Form in HAML
- resource.build_instructor
- form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= hidden_field_tag :destination, { :value => destination}
.field
= f.label :firstname, "First Name"
= f.text_field :firstname
.field
= f.label :lastname, "Last Name"
= f.text_field :lastname
.field
= f.label :email, "E-Mail"
= f.email_field :email
.field
= f.label :email_confirmation, "Confirm E-Mail"
= f.email_field :email_confirmation
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation, "Confirm Password"
= f.password_field :password_confirmation
#instructor-box
%p
%span.bold Are you an instructor?
= check_box_tag :instructor_check
%span Yes, I am an instructor
= f.fields_for :instructor do |i|
= render "/users/registrations/instructor", :form => i
I fixed the problem. It seems all too obvious now. In order for the fields_for to be cancelled out all I have to do is delete the instructor_attributes that are being created by the form in the controller. For example:
Create
# params[:user] => {:email => "justin#example.edu", ..., :instructor_attributes => { :school_url => "www.example.edu", ...}
# params[:instructor_check] => "0"
Given these parameters being passed, I can easily delete the attributes that are to be saved and the rails no longer tries to create a new record for instructor that's to be associated with user. This is literally the code I used. Not the most elegant, but it works.
params[:user].delete :instructor_attributes if params[:instructor_check] = "0"
This recognizes that no instructor profile is being created for the user and thus does not write to the table. Before it was sending back blank attributes and failing on the validations.
I suspect this might be a very simple mistake but I've spent 3 hours looking for it so I thought I might ask for some help from the community.
I'm running through Ryan Bates' excellent screencasts on Nested Models Forms and trying to apply them to my own project. The problem is the nested attribute doesn't seem to save using the form. I can get it to save through the console but it only shows up as empty brackets when going through the form.
Here's the relevant code:
The form view (using haml)
= form_for(#article) do |f|
- if #article.errors.any?
#error_explanation
%h2
= pluralize(#article.errors.count, "error")
prohibited this article from being saved:
%ul
- #article.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :title
%br/
= f.text_field :title
.field
= f.label :intro
%br/
= f.text_area :intro
= f.fields_for :subsections do |builder|
= render 'subsections_fields', :f => builder
.field
= f.label :published_at
%br/
= f.text_field :published_at
.actions
= submit_or_cancel(f)
subsection_fields form view
= f.label :header
%br/
= f.text_field :header
= f.label :order_id
= f.number_field :order_id
%br/
= f.label :body
%br/
= f.text_area :body
%br/
= f.check_box :_destroy
= f.label :_destroy, "Remove Subsection"
%br/
Controller
class ArticlesController < ApplicationController
def new
#article = Article.new
3.times { #article.subsections.build }
end
def create
#article = Article.new(params[:article])
if #article.save
flash[:notice] = "Successfully created article."
redirect_to #article
else
render :action => 'new'
end
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update_attributes(params[:article])
flash[:notice] = "Successfully updated article."
redirect_to #survey
else
render :action => 'edit'
end
end
def destroy
Article.find(params[:id]).destroy
flash[:notice] = "Succesfully destroy article."
redirect_to articles_url
end
def show
#article = Article.find(params[:id])
end
def index
#articles = Article.all
end
end
And the models
class Article < ActiveRecord::Base
attr_accessible :title, :intro
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end
class Subsection < ActiveRecord::Base
attr_accessible :header, :body, :order_id
belongs_to :article
validates :header, :presence => true
validates :body, :presence => true
end
Any help figuring this out is much appreciated.
I'm not quite sure, but try it with attr_accessible :article_id as well in your Subsection model?
Adding "attr_accessible" to a model changes the way mass assignment works in rails.
If you remove the "attr_accessible" lines in your models then all your code will work perfectly as it is.
The class method "accepts_nested_attributes_for" adds a "subsections_attributes=(value)" method to your model.
The second you add "attr_accessible" to a model you now are forced into adding extra "attr_accessible" entries for each field that you want to assign via mass assignment. i.e. when you use Article.new(params[:article]).
I hope that was clear.
I figured out the answer to this one from another question.
The answer was to set my subsections_attributes as an attr_accessible, so the above Article model should look like this (I also added published_at as an attr_accessible):
class Article < ActiveRecord::Base
attr_accessible :title, :intro, :subsections_attributes, :published_at
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end
I am using accepts_nested_attributes_for with the has_one polymorphic model in rails 2.3.5
Following are the models and its associations:
class Address < ActiveRecord::Base
attr_accessible :city, :address1, :address2
belongs_to :addressable, :polymorphic => true
validates_presence_of :address1, :address2, :city
end
class Vendor < ActiveRecord::Base
attr_accessible :name, :address_attributes
has_one :address, :as => :addressable, :dependent => :destroy
accepts_nested_attributes_for :address
end
This is the view:
- form_for #vendor do |f|
= f.error_messages
%p
= f.label :name
%br
= f.text_field :name
- f.fields_for :address_attributes do |address|
= render "shared/address_fields", :f => address
%p
= f.submit "Create"
This is the partial shared/address_fields.html.haml
%p
= f.label :city
%br= f.text_field :city
%span City/Town name like Dharan, Butwal, Kathmandu, ..
%p
= f.label :address1
%br= f.text_field :address1
%span City Street name like Lazimpat, New Road, ..
%p
= f.label :address2
%br= f.text_field :address2
%span Tole, Marg, Chowk name like Pokhrel Tole, Shanti Marg, Pako, ..
And this is the controller:
class VendorsController < ApplicationController
def new
#vendor = Vendor.new
end
def create
#vendor = Vendor.new(params[:vendor])
if #vendor.save
flash[:notice] = "Vendor created successfully!"
redirect_to #vendor
else
render :action => 'new'
end
end
end
The problem is when I fill in all the fileds, the record gets save on both tables as expected.
But when I just the name and city or address1 filed, the validation works, error message shown, but the value I put in the city or address1, is not persisted or not displayed inside the address form fields?
This is the same case with edit action too.
Though the record is saved, the address doesn't show up on the edit form. Only the name of the Client model is shown.
Actually, when I look at the log, the address model SQL is not queried even at all.
Why f.fields_for :address_attributes?
Shouldn't it be:
- f.fields_for :address do |address_fields|
= render "shared/address_fields", :f => address_fields
It's not loading the values on edit and errors because you never load address_attributes with the values from #vendor.address.
I set up AuthLogic for Rails according to the AuthLogic example: http://github.com/binarylogic/authlogic_example.
I can log on successfully to the system, but when accessing users/new.html.erb to register a new user, the form returns the following validation errors:
Email is too short (minimum is 6 characters)
Email should look like an email address.
Login is too short (minimum is 3 characters)
Login should use only letters, numbers, spaces, and .-_# please.
Password is too short (minimum is 4 characters)
Password confirmation is too short (minimum is 4 characters)
None of these errors exist in the data I am entering.
# new.html.erb
<%= form.label :login, nil, :class => "label" %><br />
<%= form.text_field :login, :class => "inputBox",
:name => "login", :type => "text" %><br />
<%= form.label :password, form.object.new_record? ? nil : "Change password", :class => "label" %><br />
<%= form.password_field :password, :class => "inputBox",
:name => "password", :type => "text" %><br />
<%= form.label "Confirm password", nil, :class => "label" %><br />
<%= form.password_field :password_confirmation, :class => "inputBox",
:name => "password_confirmation", :type => "text" %><br />
<%= form.label :email, nil, :class => "label" %><br />
<%= form.text_field :email, :class => "inputBox",
:name => "email", :type => "text" %><br />
# Users controller
def new
#user = User.new
render :layout => "forms"
end
I think the problem is that the data isn't being transferred somehow and therefore AuthLogic doesn't think the inputs are sufficient. Do you have any idea why AuthLogic is telling me the data doesn't satisfy its validation?
------MORE INFO------
# User model
class User < ActiveRecord::Base
acts_as_authentic
belongs_to :authenticable, :polymorphic => true
end
# Users controller, def create:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Account registered!"
redirect_back_or_default account_url
else
render :action => :new
end
end
Check params[:user] and see if it's sending the correct values
You can also remove the validation in the model that acts_as_authentic adding the following fields:
acts_as_authentic do |c|
c.validate_login_field = false
c.validate_email_field = false
c.validate_password_field = false
end
Just a thought, do you have mass assignment disabled somewhere higher up than your model class?
I have had this happen before where I disabled mass assignment of attributes in my initializers, then forgot to set my model's attributes as accessible using attr_accessible for all required attributes inside the model class.
One thing that is not clear in the documentation and in the videos, is that :password is still required in the attr_accessible line even though you may have crypted_password in the model (i.e. attr_accessible :password).
I'm not certain, but i'm assuming that authlogic uses this password variable to hold the clear text version before it encrypts it.
I don't recommend setting c.validate_password_field = false because this turns off encryption as well.