So, I'm trying to add a selection for a belongs_to relationship in the User Registration form.
For example:
Here's the User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
belongs_to :thing
validates_presence_of :thing
end
Thing model:
class Thing < ActiveRecord::Base
attr_accessible :name
has_many :user
validates_presence_of :name
validates :name, :uniqueness => { :case_sensitive => false }
end
So, I've added some code to the app/views/devise/registration/new.html.haml file:
%h2 Sign up
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
= devise_error_messages!
%div
= f.label :email
%br
= f.email_field :email, :autofocus => true
%div
= f.label :password
%br
= f.password_field :password
%div
= f.label :password_confirmation
%br
= f.password_field :password_confirmation
%div
= f.label :thing
%br
= f.select :thing, #things.map{ |r| [r.name, r.id] }
%div
= f.submit "Sign up"
= render "devise/shared/links"
So this all works fine, I can select the things from the select box. However, handling the submit is what I'm getting confused about. With this how it is, I'm getting a "can't mass assign protected attributes" error, which is what it should be doing.
How can I override the Devise controller to handle this? I've tried something like:
class RegistrationsController < Devise::RegistrationsController
def new
#things = Thing.all.sort_by{|e| e[:name]}
super
end
def create
#user = User.new(params[:user][:email], params[:user][:password])
#user.thing = params[:user][:thing]
super
end
end
but I get the feeling this is not anywhere close to what I'm supposed to do. Any help would be appreciated! Thanks!
Adding a relationship between two models can't be done without database changes. Rails don't change the database automatically to you, you need to write a migration for that. You can check on rails guides how to add a rails has_many relationship, and overall, I recommend take a good read in the model/view/controller sections form index before start using rails. It should take you no more than two days to read this stuff and if you understand what you are doing you will be able to do things better/faster.
So I figured it out.
I had to add a create method in the Registrations controller to override the Devise one.
class RegistrationsController < Devise::RegistrationsController
def new
#things = Thing.all.sort_by{|e| e[:name]}
super
end
def create
#things = Thing.all.sort_by{|e| e[:name]}
#user = User.new(email: params[:user][:email], password: params[:user][:password], password_confirmation: params[:user][:password_confirmation])
#user.thing = Thing.find(params[:user][:thing])
if #user.save
if #user.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_up(:user, #user)
respond_with #user, :location => after_sign_up_path_for(#user)
else
set_flash_message :notice, :"signed_up_but_#{#user.inactive_message}" if is_navigational_format?
expire_session_data_after_sign_in!
respond_with #user, :location => after_inactive_sign_up_path_for(#user)
end
else
clean_up_passwords #user
respond_with #user
end
end
end
I basically copy and pasted the one from the Devise source code and put the save code for my object in there.
Related
I am learning rails and I am trying to understand how the MVC model works with rails 4. I am practicing this by creating a form that will allow the user to upload an image with a name to the database. I am using CarrierWave to handle image storage in the database. This is what I have so far. As I am new to Rails 4, I'm not sure how all these parts connect together.
Here are my models for User and IncomePicture:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:rememberable, :validatable
has_many :expense_pictures
has_many :income_pictures
end
class IncomePicture < ActiveRecord::Base
belongs_to :user
mount_uploader :image, ImageUploader
has_one :income_text
end
Controllers:
class UserController < ApplicationController
def create
User.create(user_params)
end
private
def user_params
# required input for params
# permit - returns a version of the params hash with ony the permitted attributes
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
class IncomePicturesController < ApplicationController
def create
# create IncomePicture object with params
#income_picture = IncomePicture.new(IncomePicture_params)
#
if #income_picture.save
flash[:notice] = "Income picture successfully uploaded"
redirect_to
end
private
def IncomePicture_params
params.require(:income_picture).permit(:image, :name)
end
end
view for form:
<%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.file_field :image %>
</p>
<p><%= f.submit %></p>
<% end %>
I'm not sure how to create a form that will store the upload to the logged in user. Currently only the user login portion works.
I am getting this error when I try to run rails s
First argument in form cannot contain nil or be empty
on the line
--> <%= form_for #income_picture, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
As the error says, the first argument of form_for cannot be nil or empty which means that #income_picture, the first argument, is most probably nil. So you have to ask why this variable is nil and where should I define it.
I'm assuming that the form is under app/views/income_pictures/new.html.erb which means that the most probable action corresponding to that view is the new action under IncomePicturesController.
Add a new action in the IncomePicturesController and define #income_picture
class IncomePicturesController < ApplicationController
def new
#income_picture = IncomePicture.new
end
...
end
I'm trying to create a new record for a model that belongs to my main model on the main model's update page, but it is not being saved to the database. Basically the Company model acts as the main user model, and it has the ability to create new board members for itself on its edit registration page that devise generates. Here is what I have.
1) my company model which has many boardmembers
class Company < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :boardmembers
accepts_nested_attributes_for :boardmembers
end
2) my boardmembers model
class Boardmember < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :company
end
3) the companies controller
class Companies::RegistrationsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, only: [ :new, :create, :cancel ]
prepend_before_filter :authenticate_scope!, only: [:edit, :update, :destroy]
def create
build_resource(sign_up_params)
if resource.save
redirect_to edit_company_registration_path
else
clean_up_passwords resource
respond_with resource
end
end
def update
# For Rails 4
account_update_params = devise_parameter_sanitizer.sanitize(:account_update)
# For Rails 3
# account_update_params = params[:user]
# required for settings form to submit when password is left blank
if account_update_params[:password].blank?
account_update_params.delete("password")
account_update_params.delete("password_confirmation")
end
#company = Company.find(current_company.id)
# saves the companies boardmembers
if #company.update_attributes(account_update_params)
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #company, :bypass => true
redirect_to company_home_path
else
render 'edit'
end
end
end
3) and my application controller where i configure params
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:email, :name, :address, :jurisdiction_of_incorporation, :entity_type, :password, :password_confirmation, boardmembers_attributes: [:company_id, :id, :email, :name, :address, :secondary_email, :primary_phone, :password])
end
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:email, :name, :password, :password_confirmation)
end
end
end
My forms look something like this: edit.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<!-- here is where all the company update fields go -->
...
<!-- here are the fields for creating boardmembers
<%= f.fields_for :boardmembers, resource.boardmembers.build do |boardmember| %>
<%= boardmember.text_field :name %>
<%= boardmember.text_field :address %>
<%= boardmember.text_field :primary_phone %>
<%= boardmember.text_field :email %>
<%= boardmember.text_field :secondary_email %>
<%= boardmember.password_field :password %>
<% end %>
<%= f.submit "Update Your Account" %>
<% end %>
However the company gets its records updated but the new boardmember is not created at all. I even tried rendering a json file of the params when i click the button and it ends up looking like
{"utf8":"✓","_method":"put","authenticity_token":"mK4yd8t4m7N5rdfmHG8XKc/c+vNUdO8vryk5kYm7juw=","company": {"email":"pizzahut#email.com","name":"Pizza Comp","password":"","password_confirmation":"","entity_type":"","jurisdiction_of_incorporation":"","address":"","boardmembers_attributes":{"0":{"name":"","address":"","primary_phone":"","email":"","secondary_email":"","password":""}}},"commit":"Update Your Account","action":"update","controller":"companies/registrations"}
All of the params for the boardmembers are empty even when I fill them in. I've tried every tutorial and answer online and none of them seem to work in this case. What else could it be? Any ideas? The new record is never created. Please help.
Try this
<%= form_for([resource,resource.with_boardmembers], as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<!-- here is where all the company update fields go -->
...
<!-- here are the fields for creating boardmembers
<%= f.fields_for :boardmembers do |boardmember| %>
<%= boardmember.text_field :name %>
<%= boardmember.text_field :address %>
<%= boardmember.text_field :primary_phone %>
<%= boardmember.text_field :email %>
<%= boardmember.text_field :secondary_email %>
<%= boardmember.password_field :password %>
<% end %>
In your User model add
def with_boardmembers
self.boardmembers.build
self
end
I am working on a web-app using Devise and Rails 4. I have a User model which I have extended with 2 extra form fields such that when a user signs up he can also submit his first/last names. (based on http://blog.12spokes.com/web-design-development/adding-custom-fields-to-your-devise-user-model-in-rails-4/). I now want to add a Institution model. This model has_many :users, and a user belongs_to :institution. I want to be able to register the institution's name on the same form I register the user. I know I need a nested_attribute in my Institution model, since this is the parent, which I will show in a bit. When I try to sign up the user I get in the console: Unpermited parameters: Institutions.
My hint is that I cannot update my parent class(Institution) based upon my child class (User). Might there be a solution to this? Or has anyone experienced something similar?
class Institutions < ActiveRecord::Base
has_many :users,
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :institution
end
registrations/new.html.erb Here I have the nested form
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
.
.
<%= f.fields_for :institutions do |i| %>
<p><%= i.label :name %><br />
<%= i.text_field :institutions_attr %></p>
<% end %>
Based on the tutorial I have linked earlier, I have created a new User::ParameterSanitizer which inherits from the Devise::ParameterSanitizer and overridden the sign_up method as follows:
lib/user_sanitizer.rb
private
def sign_up
default_params.permit(:first_name, :last_name ,:email, :password, :password_confirmation, :current_password, institutions_attributes: [:id, :name])
end
Finally, my application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
protected
def devise_parameter_sanitizer
if resource_class == User
User::ParameterSanitizer.new(User, :user, params)
else
super
end
end
end
Thank you for reading!
Console params output:
{"utf8"=>"✓",
"authenticity_token"=>"JKuN6K5l0iwFsj/25B7GKDj7WEHR4DO3oaVyGxGJKvU=",
"user"=>{"email"=>"abc#foo.com",
"first_name"=>"abc",
"last_name"=>"xyz",
"institutions"=>{"name"=>"Government"},
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Sign up"}
EDIT
As suggested, I have added
params.require(resource_name).permit( :email, :first_name, :last_name, institution: [:name], :password, :password_confirmation ) and I get an *error syntax error, unexpected ',', expecting => ...nstitution: [:name], :password, :password_confirmation )*
BUT, if I re-edit to
params.require(resource_name).permit( :email, :first_name, :last_name, :password, :password_confirmation, institution: [:name] )
I get NO syntax error but I get Unpermited parameters: Institutions in the Request.
My belief is that this happens because User is a child of Institution. I have, however, been unable to find a work-around this.
config/routes.rb
Create your own registration controller like so ... (see Devise documentation for the details of overriding controllers here ...) ... which is more elegant way as opposed to doing it via the ApplicationController
devise_for :users, controllers: {registrations: 'users/registrations'}
app/controllers/users/registrations_controller.rb
Override the new method to create a Profile associated with the User model as below ... run the configure_permitted_parameters method before to sanitize the parameters (note how to add nested parameters)
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
# GET /users/sign_up
def new
# Override Devise default behaviour and create a profile as well
build_resource({})
resource.build_profile
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u|
u.permit(:email, :password, :password_confirmation, :profile_attributes => :fullname)
}
end
end
db/migrate/xxxxxxxxxxxxxx_create_profiles.rb
This is the migration that generates the Profile model (note the reference to User) ... this example profile only keeps fullname as an extension of the User but feel free to add as you wish!
class CreateProfiles < ActiveRecord::Migration
def change
create_table :profiles do |t|
t.references :user
t.string :fullname
t.timestamps
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allow saving of attributes on associated records through the parent,
# :autosave option is automatically enabled on every association
accepts_nested_attributes_for :profile
# Devise
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
app/models/profile.rb
class Profile < ActiveRecord::Base
# Associations
belongs_to :user
# Validations
validates :fullname, presence: true
end
app/views/devise/registrations/new.html
<% resource.build_profile if resource.profile.nil? %>
<%= form_for(resource, :as => resource_name,
:url => registration_path(resource_name)) do |f| %>
<ul>
<%= devise_error_messages! %>
<li class="fullname">
<%= f.fields_for :profile do |profile_fields| %>
<%= profile_fields.label :fullname %>
<%= profile_fields.text_field :fullname %>
<% end %>
</li>
<li class="email">
<%= f.label :email %>
<%= f.email_field :email, :autofocus => true %>
</li>
<li class="password">
<%= f.label :password %>
<%= f.password_field :password %>
</li>
<li class="password">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
</li>
<li>
<%= f.submit %>
</li>
<li>
<p><%= render "devise/shared/links" %></p>
</li>
</ul>
<% end %>
You must create your own registration controller to do so, here is how:
routes.rb
devise_for :users, controllers: {registrations: 'registrations'}
Controller
You must replace :your_fields by the fields you want to allow (sorry if I leave that to you, but that makes my answer more general, therefore usable for anyone that would pass by)
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
allow = [:email, :your_fields, :password, :password_confirmation]
params.require(resource_name).permit(allow)
end
end
Additional info (nested attributes + some testing)
Also note that if you are using association and accepts_nested_attributes_for you will have params structured like this
model: {field, field, field, associated_model: {field, field}}
And off course you must use the same structure in your sign_up_params method. If you need to understand this, you can change the content of sign_up_params method like this:
def sign_up_params
params.require(resource_name).permit!
end
That will allow any param, then post your form (it should pass this time) and look into your rails console to see the structure of params, finally you can set-up sign_up_params method correctly
Check this for more info http://www.railsexperiments.com/using-strong-parameters-with-nested-forms/
In your case you should use:
params.require(resource_name).permit( :email, :first_name, :last_name, institutions: [:name], :password, :password_confirmation )
Using rails 5.1 and devise 4.4.1 following is the shortest and works pretty good:
app/models/user.rb
after_initialize do
build_profile if new_record? && profile.blank?
end
app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [{ profile_attributes: :name }])
end
The key here is that you can do following without making separate controller:
permit nested attributes
build relation for form builder
in my rails app, I am running into an issue. As a heads up I am using devise.
tracks_controller.rb
def new
#track = Track.new
end
def create
#track = current_user.tracks.build(params[:content])
if #track.save
flash[:success] = "Track created!"
redirect_to #user
else
render 'static_pages/home'
end
users_controller.rb
def show
#user = User.find(params[:id])
#tracks = #user.tracks
if signed_in?
#track = current_user.tracks.build
end
end
I am logged in as a current user, and when I try to add a new track (through the current user) it is not saving.. (and instead redirects to root_url)
track.rb
class Track < ActiveRecord::Base
attr_accessible :content
belongs_to :user
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :username, :email, :password, :password_confirmation, :remember_me
# attr_accessible :title, :body
validates :username, uniqueness: { case_sensitive: false }
has_many :tracks, dependent: :destroy
end
shared/_track_form.html.erb
<%= form_for(#track) do |f| %>
<div class="track_field">
<%= f.text_area :content, placeholder: "Upload a youtube song URL...", :id => "message_area" %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
relavent section for /users/show.html.erb
<div class="span8">
<% if signed_in? %>
<section>
<%= render 'shared/track_form' %>
</section>
<% end %>
I believe the issue is in my TracksController #create method, however I just can't figure it out. any help is greatly appreciated, thanks!
In your controller create action change
#track = current_user.tracks.build(params[:content])
to this
#track = current_user.tracks.build(params[:track])
Since you used form_for(#track) the params hash will contain the :content field filled into the form.
The way you have it now the create action cant find the form :content because there isn't a form named content. content is an attribute of the Track model.
I've got two models, Users and Organizations, which have a has_many relationship using an assignments table. I have a nested resource form when the user is created, which creates an associated organization just fine. However, when creating an organization, it doesn't associate it with the user.
Here's my relevant Organizations controller code:
def new
#organization = current_user.organizations.build
end
def create
#organization = current_user.organizations.build(params[:organization])
#organization.save
end
And my models:
Organizations Assignments
class OrganizationAssignment < ActiveRecord::Base
belongs_to :user
belongs_to :organization
attr_accessible :user_id, :organization_id
end
Organizations:
class Organization < ActiveRecord::Base
validates :subdomain, :presence => true, :uniqueness => true
has_many :organization_assignments
has_many :people
has_many :users, :through => :organization_assignments
attr_accessible :name, :subdomain
end
Users:
class User < ActiveRecord::Base
has_many :organization_assignments
has_many :organizations, :through => :organization_assignments
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
accepts_nested_attributes_for :organizations
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :organizations_attributes
# attr_accessible :title, :body
end
form view:
= form_for #organization, :html => { :class => 'form-horizontal' } do |f|
- #organization.errors.full_messages.each do |msg|
.alert.alert-error
%h3
= pluralize(#organization.errors.count, 'error')
prohibited this user from being saved:
%ul
%li
= msg
= f.label :name
= f.text_field :name
= f.label :subdomain
= f.text_field :subdomain
.form-actions
= f.submit nil, :class => 'btn btn-primary'
= link_to t('.cancel', :default => t("helpers.links.cancel")), organizations_path, :class => 'btn'
I'm able to associate the organizations fine after the fact in the console, so I'm pretty sure the relationships are set up correctly in the model. Is there anything else I'm missing?
From my experience with Rails, you can't expect the relation to be made that way. Try something like this.
def create
#organization = Organization.build(params[:organization])
#organization.save
current_user.organizations << #organization
end
You might alternatively keep your code as-is, but save current_user instead of #organization.
def create
#organization = current_user.organizations.build(params[:organization])
current_user.save
end