can't mass assign nested attributes - ruby-on-rails

I've checked a few answers to similar problems, and all seem to suggest the problem is attr_accessible, but I just can't seem to get this working.
Basically I have User & Address model,and want to assign Address attributes as nested attributes. But when I save it causes the can't mass-assign attributes error shown below.
I've tried many variations of attr_accessible in below code, like :addresses_attributes, :address_attributes, :primary_address_attributes...I'm not sure why it's not working,perhaps because it's in the Member module declaration??
Can anyone help?
class Member::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
# each user may have zero or many addresses
# their primary email is also set as their login id.
has_many :addresses
has_one :address, :class_name => "Member::Address", :conditions => "is_primary = true"
accepts_nested_attributes_for :addresses, :address
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :address_attributes
validates_presence_of :email
end
My form that submits the nested fields (relevant parts of)
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => "form-horizontal"}) do |f| %>
<small>Mandatory fields marked *</small><br><br>
<% devise_error_messages! %>
<% flash.each do |name, msg| %>
<div class="alert alert-<%= name == :notice ? "success" : "error" %>">
<a class="close" data-dismiss="alert">×</a>
<%= msg %>
</div>
<% end %>
<%= f.fields_for resource.build_primary_address do |address_form| %>
<div class="control-group">
<%= address_form.label :first_name, :class => "control-label" %>
<div class="controls">
<%= address_form.text_field :first_name, :class => "input-xlarge" %>
</div>
</div>
<div class="control-group">
<%= address_form.label :last_name, :class => "control-label" %>
<div class="controls">
<%= address_form.text_field :last_name, :class => "input-xlarge" %>
</div>
</div>
<% end %>
The error shows this:
Can't mass-assign protected attributes: member_address
{"utf8"=>"✓",
"authenticity_token"=>"HrZJG2krn15veUKJVh6PgEs4wwufaeRhciHcUIn6AaM=",
"user"=>{"member_address"=>{"first_name"=>"asdf",
"last_name"=>"asdfsafd"},
"email"=>"Bob.JOnes#jones.com",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"}}

In order for rails to understand that that the primary address being submitted in the form is a nested attribute, you have to explicitly tell it the relation here:
<%= f.fields_for :address, resource.build_primary_address do |address_form| %>
. The reason is, because your model names are scoped with Member::, and rails derives form parameter names from the class names, in your params hash you end up with :member_address instead of :address. Because :address in this case is a field for which :user accepts nested attributes, :address should be represented as :address_attributes in the params hash, so make sure the latter appears in attr_accessible.

Add
attr_accessible :member_address
to the User model.
Explanation: every hash key that you want to initialize an ActiveRecord instance with must be stated as attr_accessible. Looking at your hash keys there is:
"user"=>{"member_address"=>{ ... }}
member_address key in the hash which initializes the User instance, thus it has to be included in the attr_accessible statement.

Related

Cocoon Gem: Edit view is only showing empty form fields

I am using cocoon in my Rails app for assigning employees (users) to projects (many to many connection). The creation of associations is working correctly, but each time I add another employee cocoon adds an empty form field in the edit view. None of the other cocoon form fields in the edit view are populated either. Could this be due to the usage of dropdowns (select)?
When I inspect the form in my browser I can see that each field seems to be assigned to one of the associations, but the selection is still empty.
What I would like to achieve is, that every association is displayed in a cocoon form field, so that they can be edited. Thanks for any help in advance!
My code is below (Sorry for any mess, it is my first time trying out a many to many connection of two models).
Project Edit View
<%= form_for(#project, :url => project_path, method: :patch) do |f| %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :customer %>
<%= f.text_field :customer, class: "form-control" %>
</div>
<%= f.fields_for :user_projects do |collab| %>
<% collab.hidden_field :project_id, value: #project.id %>
<%= render 'user_project_fields', f: collab %>
<% end %>
<div class="add-collaborator">
<%= link_to_add_association "add", f, :user_projects, class: "btn btn-mmc" %>
</div>
<div class="actions">
<%= f.submit "Save Changes", class: "btn btn-mmc btn-mmc-medium" %>
</div>
<% end %>
cocoon field partial
<div class="nested-fields">
<%= f.label "Select User" %>
<div class="form-group custom-form-group">
<%= f.select(:user_id, options_for_select(User.all.map { |u| [u.email, u.id] }), {include_blank: true}, {class: 'form-control'})%>
<div class="btn-user-project">
<%= link_to_remove_association "x", f, class: "btn btn-mmc-attention btn-mmc" %>
</div>
</div>
</div>
Project Model
class Project < ApplicationRecord
has_many :user_projects
has_many :users, :through => :user_projects
accepts_nested_attributes_for :user_projects, reject_if: :all_blank, allow_destroy: true
end
User Model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :user_projects
has_many :projects, :through => :user_projects
end
Project Controller
def edit
#project = Project.find(params[:id])
#project.user_projects.build
end
def update
#project = Project.find(params[:id])
#project.update(project_params)
#new_collaborator = UserProject.new(user_id: params[:user_id], project_id: params[:project_id])
#new_collaborator.save
if #project.update(project_params) && #new_collaborator.save
redirect_to projects_path
else
render :edit
end
end
private
def project_params
params.require(:project).permit(:title, :customer, :delted, user_projects_attributes: [:user_id, :project_id]).reject { |_, v| v.blank? }
end
I am guessing the mapping to the actual value is not done correctly, e.g. the value of the user_id is not marked as selected, in the options_for_select you have to add the selected value as parameter (see documentation).
However, there is a much easier version:
<%= f.collection_select(:user_id, User.all, :id, :email) %>
BTW using a gem like simple_form also makes building forms a lot more intuitive and straightforward.
That's because you're create object twice.
First time:
#project.update(project_params) # from accepts_nested_attributes_for
Second time:
#new_collaborator = UserProject.new(user_id: params[:user_id], project_id: params[:project_id])
#new_collaborator.save
P.s.
Can you show project_params method? I think I know why first object empty

Devise username validation during sign up

I added validation to my user.db and when I try to sign up it doesn't really work. When I don't enter the username, it says that that field can't be blank - which is correct. However, if the field is filled in, it still says that the field can't be blank and doesn't allow me to register. This is my model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable
validates :name,
:presence => true,
:uniqueness => {
:case_sensitive => false
}
has_many :pins
end
#:recoverable
View
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: 'form-horizontal'}) do |f| %>
<%= f.error_notification %>
<div align='center'>
<h2>Sign up</h2><br>
<div class='field_center'>
<%= f.input :name %>
<%= f.input :email %>
<%= f.input :password %>
<%= f.input :password_confirmation %>
</div>
<div class="form-actions">
<%= f.submit "Sign Up", class: "btn btn-primary btn-lg" %>
</div>
<% end %>
<br>
<%= render "devise/shared/links" %>
</div>
Not sure, if it's going to be helpful, but can you please check what parameters are sent at the POST action at the shell running the web server? Make sure the params are sent for :name attribute and they're not empty.
If the params for name are really empty in web server log, check your view. I had a similar case when a unclosed tag in erb resulted in a weird behavior with Devise (empty params as well).

NoMethodError on collection_select input

I have been trying different things to fix this and it's driving me nuts...
I have an input field:
<%= collection_select(:stakeholder, :user_id, User.all, :id, :first_name) %>
Each stakeholder has a user_id attribute, which is an integer.
When I try to render the form, I get this NoMethodError:
undefined method `id' for 2:Fixnum
Any ideas?
Edit: adding full form view
<%= simple_form_for([#project, #stakeholder]) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :email %>
<%= f.input :address %>
<%= f.input :city %>
<%= f.input :sentiment, %>
<%= f.label :tags, "Tags (separated by commas)" %>
<%= collection_select(:stakeholder, :user_id, User.all, :id, :first_name) %>
<%= f.button :submit %>
</div>
user.rb
class User < ActiveRecord::Base
belongs_to :account
has_many :stakeholders
def set_default_role
self.role ||= :user
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
end
stakeholder.rb
class Stakeholder < ActiveRecord::Base
belongs_to :project
has_many :notes
belongs_to :user
acts_as_taggable
def full_name
full_name = "#{first_name.capitalize} #{last_name.capitalize}"
end
end
Take a look at this documentation.
collection_select(object, method, collection, value_method,
text_method, options = {}, html_options = {}) public
Returns <select> and <option> tags for the collection of existing
return values of method for object's class. The value returned from
calling method on the instance object will be selected.
You seem to have an extra argument in your collection_select tag which is causing Rails to call :id as a method when it's actually a number (2).
user_id is a field in some Stakeholder object, right? So unless I'm misunderstanding how you've set up your relationships, stakeholder isn't relevant (in the sense that all you want is a user ID from your form).
This line should get you what you want.
<%= f.collection_select(:user_id, #users, :id, :first_name)

Create nested forms using Devise

I'm trying to build a nested form using Devise, keeping the devise featuring as sending an email when a new user signs up. I would like to have something like this:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.fields_for(:information) do |info| %>
<div><%= info.text_field :name, :placeholder => 'Nome' %></div>
<div><%= info.text_field :surname, :placeholder => 'Cognome' %></div>
<% end %>
<div><%= f.email_field :email, :autofocus => true, :placeholder => 'E-mail' %></div>
<div><%= f.password_field :password, :placeholder => 'Password' %></div>
<div><%= f.password_field :password_confirmation, :placeholder => 'Conferma password' %></div>
<div><%= f.submit "Registrati", class: "btn btn-large btn-info" %></div>
<% end %>
In my route.rb
devise_for :users, :controllers => { :registrations => "users" }
In my user.rb
class User < ActiveRecord::Base
has_one :information, dependent: :destroy
# Include default devise modules. Others available are:
# :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, :confirmable, :lockable
accepts_nested_attributes_for :information, update_only: true
end
and in information.rb
class Information < ActiveRecord::Base
belongs_to :user
end
in users_controller.rb
class UsersController < Devise::RegistrationsController
def new
super
resource.build_information
end
end
But nothing happen, or better the 2 fields name and surname don't appear, but I don't receive an error message
Here is what I would do,
I would move this line from the controller to the view(fields_for takes 2 parameters). Like below,
Controller
def new
super
resource.build_information #remove from here
end
View
<!--Added here -->
<%= f.fields_for(:information,resource.build_information) do |info| %>
Must work now!
OR
Just change
<%= form_for(resource, :as => res.....
to
<%= form_for(#resource, :as => res
Note "#", this will work. Remove build_information both from view and controller.

rails devise user external api

I am trying to setup a newsletter signup procedure when a new user signs up. I am using Mailchimp via gibbon to handle the newsletters. I have added a subscribe attribute to my model and a getter and setter method. I also have added the subscribe checkbox to my form. What I need to do is if the form box is checked subscribe the user to the email on creation.
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, :username, :subscribe
validates :username, presence: true
validates_uniqueness_of :username
has_many :contest_entries, dependent: :destroy
has_many :votes, dependent: :destroy
def subscribe=(join)
puts "called subscribe with #{join}"
if join
Gibbon.new.list_subscribe(:id => "blah", :email_address => self.email)
end
end
def subscribe
subscribe ||= false
end
end
Form view
<h2>Sign up</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<p><%= f.label :username %><br />
<%= f.text_field :username %></p>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :subscribe, "Subscribe to Newsletter?" %><br />
<%= f.check_box :subscribe, :checked => true %></div>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render "devise/shared/links" %>
I was hoping that by adding the attr_accessible and a method it would call the api from the contoller automatically, but it does not seem to be called.
Any advice on how to handle this? I could also use advice on how to test with RSPEC to make sure it is being called.
Thanks,
Cory
Ok I figured out the issue. The problem was with the check for join inside the setter method. Since it was a checkbox, I needed to check for a matching integer of 1.
def subscribe=(join)
begin
if join.to_i == 1
gb = Gibbon.new
result = gb.list_subscribe(:id => "blah", :email_address => self.email)
end
rescue
false
end
end

Resources