I've been grappling with this for a while and tried quite a few options, have watched the http://railscasts.com/episodes/26-hackers-love-mass-assignment?view=asciicast and http://railscasts.com/episodes/196-nested-model-form-part-1 Railscasts and have tried the Devise solution on their Github page but cannot find a solution to this issue.
I'm using Rails 3.2
I have a User which has many addresses.
User Model:
class User < ActiveRecord::Base
rolify
# 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 :name, :email, :password, :password_confirmation, :remember_me, :stripe_token, :coupon, :addresses_attributes
attr_accessor :stripe_token, :coupon
before_save :update_stripe
before_destroy :cancel_subscription
has_many :addresses
accepts_nested_attributes_for :addresses, :reject_if => :all_blank, :allow_destroy => true
def update_plan(role)
self.role_ids = []
self.add_role(role.name)
unless customer_id.nil?
customer = Stripe::Customer.retrieve(customer_id)
customer.update_subscription(:plan => role.name)
end
true
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "Unable to update your subscription. #{e.message}."
false
end
def update_stripe
return if email.include?(ENV['ADMIN_EMAIL'])
return if email.include?('@example.com') and not Rails.env.production?
if customer_id.nil?
if !stripe_token.present?
raise "Stripe token not present. Can't create account."
end
if coupon.blank?
customer = Stripe::Customer.create(
:email => email,
:description => name,
:card => stripe_token,
:plan => roles.first.name
)
else
customer = Stripe::Customer.create(
:email => email,
:description => name,
:card => stripe_token,
:plan => roles.first.name,
:coupon => coupon
)
end
else
customer = Stripe::Customer.retrieve(customer_id)
if stripe_token.present?
customer.card = stripe_token
end
customer.email = email
customer.description = name
customer.save
end
self.last_4_digits = customer.cards.data.first["last4"]
self.customer_id = customer.id
self.stripe_token = nil
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "#{e.message}."
self.stripe_token = nil
false
end
def cancel_subscription
unless customer_id.nil?
customer = Stripe::Customer.retrieve(customer_id)
unless customer.nil? or customer.respond_to?('deleted')
if customer.subscription.status == 'active'
customer.cancel_subscription
end
end
end
rescue Stripe::StripeError => e
logger.error "Stripe Error: " + e.message
errors.add :base, "Unable to cancel your subscription. #{e.message}."
false
end
def expire
UserMailer.expire_email(self).deliver
destroy
end
end
User controller:
def new
@plan = params[:plan]
if @plan && ENV["ROLES"].include?(@plan) && @plan != "admin"
super
@user.addresses.build
else
redirect_to root_path, :notice => 'Please select a subscription plan below.'
end
end
Address Model;
class Address < ActiveRecord::Base
attr_accessible :country, :county, :first_line, :postcode, :second_line, :town_city
#validates :country, :user_id, :county, :first_line, :postcode, :town_city, presence: true
belongs_to :user
end
Addresses controller:
class AddressesController < ApplicationController
def create
end
def update
end
end
In my view I am nesting the address model so I can create a user and an address in one foul swoop:
<div class="intro2">
<% content_for :head do %>
<%= tag :meta, :name => "stripe-key", :content => STRIPE_PUBLIC_KEY %>
<% end %>
<div id="stripe_error" class="alert alert-error" style="display:none" >
</div>
<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => 'card_form form-vertical form-signin' }) do |f| %>
<%= link_to '<i class="icon-step-backward"></i>''back'.html_safe, root_path %>
<h2>Sign up</h2>
<h3><%= params[:plan].titleize if params[:plan] %> Subscription Plan</h3>
<%= hidden_field_tag 'plan', params[:plan] %>
<%= f.error_notification %>
<%= display_base_errors resource %>
<%= f.input :name, :autofocus => true %>
<%= f.input :email, :required => true %>
<%= f.input :password, :required => true %>
<%= f.input :password_confirmation, :required => true %>
<%= field_set_tag 'Customer Details' do %>
<%= f.fields_for :address do |builder| %>
<%= builder.label :first_line %>
<%= builder.text_field :first_line %>
<%= builder.label :second_line %>
<%= builder.text_field :second_line %>
<%= builder.label :town_city %>
<%= builder.text_field :town_city %>
<%= builder.label :county %>
<%= builder.text_field :county %>
<%= builder.label :postcode %>
<%= builder.text_field :postcode %>
<%= builder.label :country %>
<%= builder.text_field :country %>
<% end %>
<% end %>
<% if @user.stripe_token %>
<p>Credit card acceptance is pending.</p>
<% else %>
<div class="field">
<%= label_tag :card_number, "Credit Card Number" %>
<%= text_field_tag :card_number, nil, name: nil %>
</div>
<div class="field">
<%= label_tag :card_code, "Card Security Code (CVV)" %>
<%= text_field_tag :card_code, nil, name: nil %>
</div>
<div class="field">
<%= label_tag :card_month, "Card Expiration" %>
<%= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month"}%>
<%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+10}, {name: nil, id: "card_year"}%>
</div>
<div class="field">
<%= f.input :coupon, :label => 'Promotional Coupon (if any)' %>
</div>
<% end %>
<%= f.hidden_field :stripe_token %>
<%= f.button :submit, 'Sign up', :class => 'btn-primary' %>
<% end %>
</div>
The error message I get in the browser is:
ActiveModel::MassAssignmentSecurity::Error at /users Can't mass-assign protected attributes: address
The request parameters are:
{"utf8"=>"✓", "authenticity_token"=>"+69wR2xYkBARu+JlsnizyF+0hrRUXo6Z+XGAipw98NY=", "plan"=>"silver", "user"=>{"name"=>"Bob", "email"=>"[email protected]", "password"=>"12345678", "password_confirmation"=>"12345678", "address"=>{"first_line"=>"7a Annette Road", "second_line"=>"Bob's Landing", "town_city"=>"London", "county"=>"Greater London", "postcode"=>"N76ET", "country"=>"UK"}, "coupon"=>"", "stripe_token"=>"tok_103NDO2KdYRHhGilKFAqsYaA"}, "action"=>"create", "controller"=>"registrations"}
As recommended on the Devise Github page I have tried putting the following in my application controller:
before_filter :configure_permitted_parameters, if: :devise_controller?
and
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:addresses_attributes) }
end
To no avail. (I also tried permitting 'address' and 'addresses')/
Any solutions really appreciated. (Incidentally, if you notice other code which is not optimal it would be great to know this too - keen to pick up good habits).
Thanks
<%= f.fields_for :addresses do |builder| %>
instead of<%= f.fields_for :address do |builder| %>
? - scaryguy@user.addresses.build
- scaryguy