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.
Related
So I have a User model and a Contract model with a many to many relationship. Contract belongs to multiple users but with different ids so i can do this
Contract.find(params[:id]).creator.email
Contract.find(params[:id]).leader.email
Contract.find(params[:id]).buyer.email
Contract.find(params[:id]).seller.email
User.find(params[:id]).contracts
In my form I have this, so I can set each id to an already created user
<%= simple_form_for(#contract) do |f| %>
<% if #contract.errors.any? %>
<div id="error_explanation">
<h3><%= pluralize(#contract.errors.count, 'error') %> prohibited this contract from being saved:</h3>
<ul>
<% #contract.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label :creator_id %><br>
<%= f.select(:creator_id, User.all.map { |c| [c.email, c.id] }) %>
</div>
<div class="form-group">
<%= f.label :leader_id %><br>
<%= f.select(:leader_id, User.all.map { |c| [c.email, c.id] }) %>
</div>
<div class="form-group">
<%= f.label :buyer_id %><br>
<%= f.select(:buyer_id, User.all.map { |c| [c.email, c.id] }) %>
</div>
<div class="form-group">
<%= f.label :seller_id %><br>
<%= f.select(:seller_id, User.all.map { |c| [c.email, c.id] }) %>
</div>
<% end %>
My contract is created with each user id correctly set but when I check the user's contracts, I can only find the same contract if user was the seller (so the last div in the form)
Edit:
Here's my two models.
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :contract, :foreign_key => 'creator_id'
has_many :contract, :foreign_key => 'leader_id'
has_many :contract, :foreign_key => 'buyer_id'
has_many :contract, :foreign_key => 'seller_id'
validates :email, presence: true, uniqueness: true
validates :role, presence: true
end
class Contract < ApplicationRecord
belongs_to :creator, :class_name => 'User', :foreign_key => 'creator_id'
belongs_to :leader, :class_name => 'User', :foreign_key => 'leader_id'
belongs_to :buyer, :class_name => 'User', :foreign_key => 'buyer_id'
belongs_to :seller, :class_name => 'User', :foreign_key => 'seller_id'
end
Contract controller:
class ContractsController < ApplicationController
before_action :set_contract, only: [:show, :edit, :update, :destroy]
def index
#contracts = Contract.all
end
def show
#contract = Contract.find(params[:id])
end
def new
#contract = Contract.new
end
def edit
end
def create
#contract = Contract.new(contract_params)
if #contract.save
redirect_to contracts_path, notice: 'Le contract a été créé avec succès'
else
render :new
end
end
def update
if #contract.update(contract_params)
redirect_to contract_path
else
render :edit
end
end
def destroy
#contract.destroy
redirect_to contracts_path
end
private
def set_contract
#contract = Contract.find(params[:id])
end
def contract_params
params.require(:contract).permit(:creator_id,
:leader_id,
:buyer_id,
:seller_id)
end
end
I need to have each of the selectors updating the chosen user but only the last one is working and I can't figure out how to make it work.
I feel like this approach can work but I'm new in rails and maybe I'm using the wrong method.
I tried a simple has_and_belongs_to_many relationship, creating users like explained here http://guides.rubyonrails.org/form_helpers.html under 'Building Complex Forms' but I lost the distinction between each specific user in the form when I set them with a field-for
Hope I'm clear enough, thanks !
Alright, so you have a couple of problems with your model definition.
Your relationship needs to be plural if you are using has_many, so it'll be has_many :contracts. This is most probably what is causing your problem.
It's not the best idea to have multiple foreign keys to the same table. Instead have one has_many :contracts, foreign_key: 'contractor_id'. But then use a separate variable to define the role of the User. And in Contract you'll have belongs_to :user, :foreign_key => 'contractor_id'. You can then specify custom accessor_methods if you want to call creator, leader, etc
Models
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :roles, :dependent => :destroy, :inverse_of => :user
has_many :companies, :through => :roles
accepts_nested_attributes_for :roles, :limit => 1, :allow_destroy => true
end
class Role < ActiveRecord::Base
belongs_to :user, :inverse_of => :roles
belongs_to :company, :inverse_of => :roles
accepts_nested_attributes_for :company
end
class Company < ActiveRecord::Base
has_many :roles, :dependent => :destroy, :inverse_of => :user
has_many :users, :through => :roles
validates :name, presence: true
end
Custom Devise Registration Controller
class RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
build_resource({})
#role = resource.roles.build(role: "owner", active: 1, default_role: 1)
#company = #role.build_company
set_minimum_password_length
yield resource if block_given?
respond_with self.resource
end
protected
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, roles_attributes: [ company_attributes: [ :id, :name ] ] )
end
end
HTML
<%= form_for(resource, :html => {:class => "form-signin" }, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render partial: "shared/flash" %>
<%= devise_error_messages! %>
<h1 class="form-signin-heading text-muted">Register</h1>
<%= f.email_field :email, class: "form-control", placeholder: "Email", autofocus: true %>
<%= f.password_field :password, class: "form-control", placeholder: "Password", autocomplete: "off" %>
<%= f.password_field :password_confirmation, class: "form-control", placeholder: "Password Confirmation", autocomplete: "off" %>
<%= f.fields_for :roles, resource.roles.build do |r| %>
<%= r.fields_for :company, resource.roles.build.build_company do |c| %>
<%= c.text_field :name, class: "form-control", placeholder: "Company", autocomplete: "off" %>
<% end %>
<% end %>
<button class="btn btn-lg btn-primary btn-block" type="submit">
Register
</button>
<% end %>
This works - my intermediate Role is created with the id_user and id_company. The problem is I want to set the some additional fields in the newly created Role. for example I have a :role column that I want to set to 'owner' as this is the a brand new company and the user that signed up is the owner.
I want to do this in the controller to prevent any mass assignment issues from the user submitted form.
Do I need to set this somehow in the custom devise registration controller and create a full custom create action?
I admit I am likely not explaining this well as I am a bit of a newbie on the whole nested forms and active record etc.
UPDATE
It's not pretty but I just pasted this at the end of my new controller:
def set_minimum_password_length
if devise_mapping.validatable?
#minimum_password_length = resource_class.password_length.min
end
end
UPDATE 2
I had copied the master code vs the current version code. Once I fixed that it's all good.
I would overwrite the create action so that you can hardcode (and the user can't mess with) the role attributes.
You're assigning them in the new action, but you'd need to have hidden fields so they get passed to create and persist into the database. However, that's not a good idea because anyone can edit the HTML and change those values. It's better to do this in the create action instead like so:
class RegistrationsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
# The last role should be the one created in the form
# We set the values here so they get saved and they aren't passed in the form
resource.roles.last.assign_attributes(role: "owner", active: 1, default_role: 1)
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
end
Not sure why this isn't working but I've been mis-guided I think....I'd rather not re-route but simply have the photo uploaded in the current landing_welcome page.. not be transfered to an update template.
error:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!(person_params)
redirect_to #user
end
Users_controller.rb
class UsersController < ApplicationController
def update
#user = current_user.photos.find(params[:id])
#user.update_attributes!
redirect_to #user
end
def show
end
end
landing_welcome.html.erb
<div class="tab-pane" id="tab2">
<%= nested_form_for current_user, :html=>{:multipart => true } do |f| %>
<%= f.fields_for :photos do |p| %>
<p>
<%= image_tag(p.object.file.url) if p.object.file? %>
<%= p.label :file %><br />
<%= p.file_field :file %>
</p>
<%= p.link_to_remove "Remove this attachment" %>
<% end %>
<%= f.link_to_add "Add photos to your profile", :photos %>
<br /><br />
<p><%= f.submit %></p>
<% end %>
</div>
routes.rb
root to: "home#landing"
devise_for :users, :controllers => {:registrations => "users/registrations",
:sessions => "users/sessions",
:passwords => "users/passwords"}
get "welcome", to: "home#landing_welcome"
devise_scope :user do
# get "edit/edit_account", :to => "devise/registrations#edit_account", :as => "account_registration"
get 'edit/edit_account' => 'users/registrations#account_registration', as: :edit_account
end
patch '/users/:id', to: 'users#update', as: 'user'
photo.rb
class Photo < ActiveRecord::Base
attr_accessible :file
belongs_to :attachable, :polymorphic => true
mount_uploader :file, FileUploader
end
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email,
:password,
:password_confirmation,
:zip,
:gender,
:remember_me,
:first_name,
:last_name,
:birthday,
:current_password,
:occupation,
:address,
:interests,
:aboutme,
:profile_image,
:photos_attributes
has_many :photos, as: :attachable
accepts_nested_attributes_for :photos
mount_uploader :profile_image, ProfileImageUploader
validates :gender, :presence => true
def number_of_users
User.all.count
end
end
For lack of a better answer, I think your woes lie in the query generated by your app:
Couldn't find Photo with id=16 [WHERE "photos"."attachable_id" = $1 AND "photos"."attachable_type" = $2]
Two factors are present here:
Why is your attachable_id being called as $1?
Why is your attachable_type a number, not a string?
Polymorphic Association
Your query is trying to load a Photo with ID=16, however, your query is also trying to validate the model, to satisfy the polymorphic association. This is where the error is coming from
As you've not stated which route / page this error is showing, I can only speculate as to the cause of the problem:
#user = current_user.photos.find(params[:id])
This query is really bad for a number of reasons:
You're using the current_user object directly. I might be wrong here, but this is used by Devise / Warden to store relative information about the logged-in user, and is not a real ActiveRecord object (with relational data etc)
You're trying to .find on top of a relation (current_user.photos)
Although this might be incorrect, I would look at doing this for that query:
#photo = User.joins(:photos).find(current_user.id).where("photos.id = ?", params[:id])
Then you can perform the updates you require
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.
I have Users who bet on matches. A single bet is called "Tipp" and the users predict the match score in "tipp.tipp1" and "tipp.tipp2"
I have problems with my form which is supposed to save "tipps" of users.
With the code below I get "Can't mass-assign protected attributes: tipp" although i have set "accepts_nested_attributes_for :tipps" and "attr_accessible :tipps_attributes".
I hope I have provided all the necessary code. Thanks in advance for your help!
Here is the parameters output:
Parameters:
{
"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"mPPpCHjA3f/M2l1Bd3ffO1QUr+kdETGkNE/0CNhbJXE=",
"user" =>{
"tipp"=>{
"6"=>{"tipp1"=>"4","tipp2"=>"6"},
"7"=>{"tipp1"=>"-1","tipp2"=>"-1"},
"8"=>{"tipp1"=>"-1","tipp2"=>"-1"}
}
},
"commit"=>"Update User",
"user_id"=>"1"
}
Shortened Code:
Controllers:
1) Users
class UsersController < ApplicationController
def edit_tipps
#user = current_user
end
def update_tipps
#user = current_user
if #user.update_attributes(params[:user])
flash[:notice] = "success (maybe)"
redirect_to user_edit_tipps_path(#user)
else
flash[:error] = "errors"
redirect_to user_edit_tipps_path(#user)
end
end
Models:
1) Users
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :tipps_attributes
has_many :tipps
accepts_nested_attributes_for :tipps
end
2) Tipps
class Tipp < ActiveRecord::Base
attr_accessible :match_id, :points, :round_id, :tipp1, :tipp2, :user_id
belongs_to :user
end
My Form:
<%= form_for #user, :url => { :action => "update_tipps" } do |user_form| %>
<% #user.tipps.each do |tipp| %>
<%= user_form.fields_for tipp, :index => tipp.id do |tipp_form|%>
<%= tipp_form.text_field :tipp1 %><br/>
<%= tipp_form.text_field :tipp2 %><br/>
<% end %>
<% end %>
<%= submit_or_cancel(user_form) %>
<% end %>
Instead of doing what you did,
you could try either:
1.
Instead of:
<% #user.tipps.each do |tipp| %>
<%= user_form.fields_for tipp, :index => tipp.id do |tipp_form|%>
I would do this:
<%= user_form.fields_for :tipps do |tipp_form| %>
Or:
2.
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :tipps_attributes, :tipps
Goodluck