RoR : Nested forms with devise - ruby-on-rails

I am new to RoR and I thought I could ask you some help. I didn't find the specific answer I am looking for.
I have a problem with a modelisation I want to make using Devise. Devise sets up a Member model, and I want to have a SuperMember model which have more attributes than Member, and some different views.
I want to set up a nested form to create a SuperMember, while automatically creating the Member account in the background.
Member.rb (generated by devise)
class Member < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
SuperMember.rb
class Supermember < ActiveRecord::Base
attr_accessible :first_name, :last_name
belongs_to :member, :dependent => :destroy
accepts_nested_attributes_for :member
end
Supermembers.controllers.rb
def new
#supermember = Supermember.new
#supermember.member = Supermember.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #supermember }
end
end
def create
#supermember = Supermember.new(params[:supermember])
respond_to do |format|
if #supermember.save
format.html { redirect_to #supermember, notice: 'Supermember was successfully created.' }
format.json { render json: #supermember, status: :created, location: #supermember }
else
format.html { render action: "new" }
format.json { render json: #supermember.errors, status: :unprocessable_entity }
end
end
I tried to create a nested form in order to generate both the member and the supermember :
<%= simple_form_for(#supermember) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :first_name %>
<%= f.input :last_name %>
</div>
<% # Devise member %>
<%= f.fields_for :member do |m| %>
<div class="form-inputs">
<%= m.input :email, :required => true, :autofocus => true %>
<%= m.input :password, :required => true %>
<%= m.input :password_confirmation, :required => true %>
</div>
<% end %>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
The problem is that when I submit this form, I get the following error message :
Can't mass-assign protected attributes: member_attributes
Application Trace | Framework Trace | Full Trace
app/controllers/supermembers.rb:44:in `new'
app/controllers/supermembers.rb:44:in `create'
I really don't understand how to fix it. Could you help me on this one?
Thank you very much!

You need to allow Supermember to accept mass assignment of the member attributes
class Supermember < ActiveRecord::Base
attr_accessible :first_name, :last_name, :member_attributes
...
end

If what you're trying to do is add attributes to member, then it is perfectly OK to do so. There's no need to create a supermember for that purpose only (of course, if you have some other agenda then go ahead...).
Device doesn't care if you add attributes to the model, even if it was generated by it.

Related

Rails nested form attributes not showing on edit

I'm working on an app that has and agency which has many branches and both the agency and the branches can have addresses. I have created the models for them as shown below:
# agency.rb
has_many :branches
has_one :address
accepts_nested_attributes_for :branches, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :address, reject_if: :all_blank, allow_destroy: true
# branch.rb
belongs_to :agency
has_one :address
accepts_nested_attributes_for :address, reject_if: :all_blank
# address.rb
belongs_to :agency
belongs_to :branch
I have an agency form which should allow me to create/edit an agency and it's address as well as add/edit/delete branches and their addresses too.
Here is the agency form:
<%= form_for(#agency) do |f| %>
<div class="box-body">
# errors and messages etc etc ...
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: 'form-control' %>
</div>
# more agency fields
<%= f.fields_for :address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
#other fields which all work fine
<div id="branches">
<%= f.fields_for :branches do |branch| %>
<%= render 'branch_fields', f: branch %>
<% end %>
<div class="links">
<%= link_to_add_association 'add branch', f, :branches %>
</div>
</div>
</div>
<%= f.submit 'Save Changes', class: 'btn btn-primary' %>
<% end %>
Here are the branch fields (partial form):
# branch name ...
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
# I will explain this bit below
<% if f.object.new_record? %>
<% f.object.build_address %>
<% end %>
###
<%= f.fields_for :address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
# other branch fields that work fine
<%= link_to_remove_association "remove branch", f %>
And here is the agencies controller:
class AgenciesController < ApplicationController
before_action :authenticate_user!
before_action :set_agency, only: [:show, :edit, :update]
# before_action :build_address, only: [:new, :edit]
def index
#agencies = Agency.all
end
def show
end
def create
#agency = Agency.new(agency_params)
respond_to do |format|
if #agency.save
format.html { redirect_to #agency, notice: 'Agency was successfully created.' }
format.json { render :show, status: :created, location: #agency }
else
format.html { render :new }
format.json { render json: #agency.errors, status: :unprocessable_entity }
end
end
end
def new
#agency = Agency.new
#agency.build_address
#agency.build_domain_name
#agency.branches.build
#agency.services.build
# #agency.branches.build_addresses
# #agency.branches.each do |branch|
# branch.build_address
# end
end
def edit
end
def update
respond_to do |format|
if #agency.update(agency_params)
format.html { redirect_to #agency, notice: 'Agency was successfully updated.' }
format.json { render :show, status: :ok, location: #agency }
else
format.html { render :edit }
format.json { render json: #agency.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_agency
#agency = Agency.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def agency_params
params.require(:agency).permit(:name, :logo, :description, :phone_number, :alt_phone_number, :email, :alt_email,
services_attributes:
[:id, :name, :description, :_destroy],
address_attributes:
[:id, :first_line, :second_line, :third_line, :city_town, :post_code, :_destroy],
domain_name_attributes:
[:id, :domain],
branches_attributes:
[:id, :name, :description, :phone_number, :alt_phone_number, :_destroy, :email, :alt_email,
services_attributes:
[:id, :name, :description, :_destroy],
address_attributes:
[:id, :first_line, :second_line, :third_line, :city_town, :post_code, :_destroy]
]
)
end
end
Apologies for the lengthy code.
The problem I was facing initially was that when I was adding a new branch, the address fields were not showing up. After some digging, I found a question on SO where someone suggested running f.object.build_address
just before calling the fields for the branch. This worked fine for creating a new branch on the agency form but after saving, and returning to the edit agency page, the address fields for the branch were blank. However, the address was saved correctly to teh database. I think this was caused bu the build_address line overriting the content when editing which is why I attempted to wrap it in the if loop but I'm almost certain this is wrong.
How can I correctly build the nested branch address attributes in the controller so that the fields are shown correctly when adding a new agency and or branch? And how can I ensure that the saved values are displayed correctly when I come back to edit the agency and branch(es) later.
I have done a lot of reading and digging and I cant see any of the normal issues like wrong tags used (<%= / <%) to no avail. Any help would be most appreciated.
Try with these modifications:
<%= f.fields_for #agency.address do |address| %>
<%= render 'addresses/address_fields', f: address %>
<% end %>
#other fields which all work fine
<div id="branches">
<%= f.fields_for #agency.branches do |branch| %>
<%= render 'branch_fields', f: branch %>
<% end %>

Rails + Devise Nested Registration form undefined method 'build_address'

Hi this question is basically the same as this one, which had no responses. I'm trying to combine the Devise registration form to include fields that produce not only a "user", but a "customer" object, an "account" object for that customer, and an "address" for that customer.
When visitor clicks "Sign Up", the registration form should include the standard Devise stuff, but also the fields for the creation of the other objects. Instead, I get this error:
NoMethodError in Registrations#new
undefined method `build_address' for #
Extracted source (around line #6):
<div class="panel panel-default" style="width: 14em;">
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
<%= form_for(resource, as: resource_name, url: >registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<h3>User Info</h3>
Rather than explaining all the relationships, here are the models:
user.rb
class User < ActiveRecord::Base
before_create :generate_id
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
has_one :administrator
has_one :customer
has_many :accounts, through: :customer
accepts_nested_attributes_for :customer, :allow_destroy => true
accepts_nested_attributes_for :accounts, :allow_destroy => true
has_one :address, through: :customer
accepts_nested_attributes_for :customer, :allow_destroy => true
accepts_nested_attributes_for :address, :allow_destroy => true
validates_uniqueness_of :email, :case_sensitive => false
validates_uniqueness_of :id
validates :username,
:presence => true,
:uniqueness=> {
:case_sensitive => false
}
# User ID is a generated uuid
include ActiveUUID::UUID
natural_key :user_id, :remember_created_at
belongs_to :user
# specify custom UUID namespace for the natural key
uuid_namespace "1dd74dd0-d116-11e0-99c7-5ac5d975667e"
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :timeoutable, :recoverable, :trackable, :validatable
# Generate a random uuid for new user id creation
def generate_id
self.id = SecureRandom.uuid
end
# Allow signin by either email or username ("lower" function might have to be removed?)
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions.to_h).first
end
end
end
customer.rb
class Customer < ActiveRecord::Base
belongs_to :user
has_one :address
has_many :accounts
validates :phone1, :firstname, :lastname, presence: true
end
account.rb
class Account < ActiveRecord::Base
belongs_to :customer
belongs_to :user
has_one :acct_type
has_many :acct_transactions
validates :acct_type, presence: true
end
address.rb
class Address < ActiveRecord::Base
belongs_to :customer
belongs_to :user
validates :zip_code, presence: true
validates :address1, presence: true
has_one :zip_code
has_one :state, through: :zip_code
end
The two controllers in question:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
# GET /users/sign_up
def new
#user = current_user
#customer = nil ##user.customer
#account = nil ##customer.account
#address = nil ##customer.address
# Override Devise default behavior and create a customer, account, and address as well
build_resource({})
resource.build_customer
respond_with self.resource
build_resource({})
resource.build_account
respond_with self.resource
build_resource({})
resource.build_address
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u|
.permit(:username, :email, :password, :password_confirmation,
:customer_attributes => [:phone1, :phone2, :title, :firstname, :lastname],
:account_attributes => :acct_type,
:address_attributes => [:address1, :address2, :zip_code])
}
end
end
addresses_controller.rb (The important parts)
def new
#customer = current_user.customer
#address = #customer.address.build(:customer_id => #customer.id,
:address1 => nil,
:address2 => nil,
:zip_code => nil)
end
def create
#customer = current_user.customer
#address = #customer.address.build(:customer_id => #customer.id,
:address1 => nil,
:address2 => nil,
:zip_code => nil)
respond_to do |format|
if #address.save
format.html { redirect_to #address, notice: 'Address was successfully created.' }
format.json { render :show, status: :created, location: #address }
else
format.html { render :new }
format.json { render json: #address.errors, status: :unprocessable_entity }
end
end
end
And here is the view where the exception is raised (It's really long so actually the important parts):
<h1>Create an account</h1>
<div class="form-group">
<div class="panel panel-default" style="width: 14em;">
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<h3>User Info</h3>
<div class="form-group">
<!-- fields for User object -->
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username, autofocus: true %>
</div>
...
<!-- fields for Customer object -->
<%= f.fields_for :customer do |customer_fields| %>
<div class="field">
<%= customer_fields.label :firstname %>
<%= customer_fields.text_field :firstname %>
</div>
...
<!-- fields for Account object -->
<%= f.fields_for :account do |account_fields| %>
<div class="field">
<%= account_fields.label :acct_type %>
<%= account_fields.text_field :acct_type %>
</div>
<% end %>
<!-- fields for Address object -->
<%= f.fields_for :address do |address_fields| %>
<div class="field">
<%= address_fields.label :address1 %>
<%= address_fields.text_field :address1 %>
</div>
...
The exception is pointing to the block of statements at the top...
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
... which has given me trouble before. Before the current error I was getting a similar error from the second line ("build_account"). But that turned out to be a pluralization issue, which I believe I've fixed. Since the HTML is read sequentially, it would seem that there is no problem with the first two build_ methods. Why is there then a problem with the build_address method?
I need to fix this error before I can know if the whole thing will actually work or not. Any ideas?
Thanks
It's Rails 4.1.8 / Devise 3.4.1
The trouble turned out to be the syntax I was using create multiple resource objects. It would pass one, but ignored the rest. What I ended up doing to make it work (or at least make the error go away) was to override the build_resource method to accept an array of parameters for each object to be instantiated:
def new
#user = current_user
build_resource({})
self.resource[:customer => Customer.new, :account => Account.new, :address => Address.new]
respond_with self.resource
end
def build_resource(hash=nil)
hash ||= params[resource_name] || {}
self.resource = resource_class.new(hash)
end
def create
# Override Devise default behavior and create a customer, account, and address as well
resource = build_resource(params[:user])
if(resource.save)
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
render :action => "new"
end
end
Also, I removed the three lines at the top of the form view as they attempted to do some sort of pre-validation in the view and just caused problems. Plenty of validation will happen when the form is submitted. This seems to be doing something good. Now I'm working with the form view and having trouble getting each part to render. Fields_for is rendering fields for User and Account models, but not Customer or Address.

Comment functionality in Rails. undefined method `first_name' for nil:NilClass

One thing I could never do properly is implement a comment feature. I'm not leaving my computer until I learn to do it.
The error is thrown on this line:
<strong><%= comment.user.first_name %></strong>
Apparently user is nil; but why? And what do I have to do to get this to work?
A comment should belong to a guide and a user. Users and guides both have many comments.
I started with
rails g scaffold comment body:text guide:references user:references
and then migrated the database. I completed the model associations as well.
Here is my guides controller show action:
def show
#guide = Guide.find(params[:id])
#comment = #guide.comments.build
end
Here is the part of the Guide show view that deals with comments:
<h3>Comments</h3>
<% #guide.comments.each do |comment| %>
<div>
<strong><%= comment.user.first_name %></strong>
<br />
<p><%= comment.body %></p>
</div>
<% end %>
<%= render 'comments/form' %>
Here is the comment form partial:
<%= simple_form_for(#comment) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :body %>
<%= f.association :user %>
<%= f.association :guide %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
User.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, #:recoverable,
:rememberable, :trackable, :validatable
validates :first_name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :runescape_username, presence: true
has_many :guides
has_many :comments
acts_as_voter
def user_score
self.guides.inject(0) { |sum, guide| sum += guide.score }
end
end
Comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :guide
end
Comments controller create action:
def create
#comment = Comment.new(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
Replace the line
#comment = Comment.new(comment_params)
with
#comment = current_user.comments.build(comment_params)
in your Comments#create action.
You get this error because you don't assign current_user to created Comment. That's why comment.user returns nil.
As stated by AndreDurao, you can also validate user_id presence in Comment model, like this:
class Comment
validates_presence_of :user
# ...
end
for getting rid of that error try this <%= comment.user.try(:first_name) %>

Rails - ArgumentError in UsersController#create - too few arguments

This is probably really basic but I can't seem to figure it out.
Essentially, when I try to create a new user using a form, and the user details are already present and not unique, I receive he following error:
ArgumentError in UsersController#create
too few arguments
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:61:in `format'
app/controllers/users_controller.rb:61:in `create'
Here's my create action in my user_controller.rb:
# POST /users
# POST /users.xml
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'User successfully created' and redirect_to :action=>"index"
else
format.html { render :action => "new" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
And here's my user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :username, :password, :password_confirmation, :remember_me
validates :email, :username, :presence => true, :uniqueness => true
end
Here's also my form:
<%= simple_form_for(#user) do |f| %>
<div class="field">
<%= f.input :username %>
</div>
<div class="field">
<%= f.input :email %>
</div>
<div class="field">
<%= f.input :password %>
</div>
<div class="field">
<%= f.input :password_confirmation %>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<% end %>
What's format in this case?
There's no respond_to block, put it back in! You're referencing some other format.
The exact layout of the code has changed a bit through different rails version (please post what version you are using - check the Gemfile).
This example is for rails 3+ (it was generated using 3.2.5 but should apply to all 3+ or at least 3.1+ versions)
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'Blob was successfully created.' }
format.json { render json: #user, status: :created, location: #user}
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
p.s. Good choice with simple_form, it makes life MUCH easier!

Rails 3.2 - Why is simple_form is not generating any markup for my nested attributes?

I've spent about 5 straight hours at this and keep ending up back at square one...time to ask for time help!
I am using Rails 3.2, devise and simple_form, I am trying to build a form that will allow a user to register (email, password) & allow them to create a simple listing object - all on the one page. However none of my nested attributes for the user are appearing on the markup when the /listings/new page loads & I cannot figure out why.
Here is what I have:
Listing controller:
def new
#listing = Listing.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #listing }
end
end
Listing model:
class Listing < ActiveRecord::Base
has_one :address
belongs_to :category
belongs_to :user
accepts_nested_attributes_for :address
accepts_nested_attributes_for :user
end
User model:
class User < ActiveRecord::Base
has_many :listings
devise :database_authenticatable, :registerable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
New Listings Form:
<%= simple_form_for(#listing) do |f| %>
<%= f.label :listing_type %>
<%= f.collection_select :listing_type, [["Creative","Creative"]], :first, :last%>
<%= f.simple_fields_for :user do |u| %>
<%= u.label :email %>
<%= u.input_field :email %>
<%= u.label_for :password %>
<%= u.input_field :password %>
<%= u.label_for :password_confirmation %>
<%= u.input_field :password_confirmation %>
<% end %>
<% end %>
My head is melted looking at this, any help is much appreciated!
Railscasts' Nested Model Form would be a good tutorial for you.
Also, what it sounds like you'd want to do is call Users#new, not Listings#new. Usually you make a form for the thing (User) which has_many of something else (Listings). So you want to make a form for a new User, not a new listing. If you take this route, then in Users#new in your controller, do something like
#user = User.new
#user.listings.build
....
If you want to keep it how it is, you might be able to do
#listing.user.build
But I'm not sure if that'll work since you're doing it in the opposite direction as I described above.
You need a new User object.
Change
<%= f.simple_fields_for :user do |u| %>
to
<%= f.simple_fields_for :user, User.new do |u| %>
It should be work.

Resources