Validation on a Complex Model for a multi-page form - ruby-on-rails-3

I'm trying to write a registration using devise and active merchant. The form is complex in that my user object looks like this:
class User < ActiveRecord::Base
include ActiveMerchant::Utils
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
# Setup accessible (or protected) attributes
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :first_name,
:subscription_attributes, :last_name, :zipcode,
:payment_profile_attributes, :customer_cim_id, :payment_profile_id
...
# Track multi-page registration
attr_writer :current_step
...
# Setup Payment Profile element (authorize.net billing profile)
has_one :payment_profile, :dependent => :delete
accepts_nested_attributes_for :payment_profile
Now the PaymentProfile class has its own children, two items from active merchant:
require 'active_merchant'
class PaymentProfile < ActiveRecord::Base
include ActiveMerchant::Billing
include ActiveMerchant::Utils
validate_on_create :validate_card, :validate_address
attr_accessor :credit_card, :address
belongs_to :user
validates_presence_of :address, :credit_card
def validate_address
unless address.valid?
address.errors.each do |error|
errors.add( :base, error )
end
end
end
def address
#address ||= ActiveMerchant::Billing::Address.new(
:name => last_name,
:address1 => address1,
:city => city,
:state => state,
:zip => zipcode,
:country => country,
:phone => phone
)
end
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
errors.add( :base, message )
end
end
end
def credit_card
#credit_card ||= ActiveMerchant::Billing::CreditCard.new(
:type => card_type,
:number => card_number,
:verification_value => verification_code,
:first_name => first_name,
:last_name => last_name
)
#credit_card.month ||= card_expire_on.month unless card_expire_on.nil?
#credit_card.year ||= card_expire_on.year unless card_expire_on.nil?
return #credit_card
end
Now I've overrided the RegistrationsController from Devise to handle the multi-page form using the solution from Ryan Bates multi-page form screencast (http://railscasts.com/episodes/217-multistep-forms). I had to tweak it a bit to get it working with Devise, but I was successful. Now because Ryan's multi-page form simply asked for different fields from the same model on different pages, he was able to override his valid? method by adding an :if block to his validate method a la:
validates_presence_of :username, :if => lambda { |o| o.current_step == "account" }
But in my case, I'm asking for all the fields on the first form from my parent model (User), and then asking for the all the fields from my two grandchild models (User:PaymentProfile:Address, User:PaymentProfile:Credit_Card) on teh second page.
The problem I'm facing is that although PaymentProfile.valid? returns errors based on ActiveMerchant's logic, the form itself doesn't render or even display those errors. The view code for the billing page looks like this:
<h2>Payment Details</h2>
<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<%= f.semantic_fields_for :payment_profile do |p| %>
<%= p.semantic_fields_for :address do |a| %>
<%= a.inputs "Billing Information", :id => "billing" do %>
<%= a.input :type, :label => "Credit Card", :as => :select, :collection => get_creditcards %>
<%= a.input :number, :label => "Card Number", :as => :numeric %>
<%= a.input :card_expire_on, :as => :date, :discard_day => true, :start_year => Date.today.year, :end_year => (Date.today.year+10), :add_month_numbers => true %>
<%= a.input :first_name %>
<%= a.input :last_name %>
<%= a.input :verification_code, :label => "CVV Code" %>
<% end %>
<% end %>
<%= f.semantic_fields_for :credit_card do |c| %>
<%= c.inputs "Billing Address", :id => "address" do %>
<%= c.input :address1, :label => "Address" %>
<%= c.input :city %>
<%= c.input :state, :as => :select, :collection => Carmen::states %>
<%= c.input :country, :as => :select, :collection => Carmen::countries, :selected => 'US' %>
<%= c.input :zipcode, :label => "Postal Code" %>
<%= c.input :phone, :as => :phone %>
<% end %>
<% end %>
<% end %>
<%= f.commit_button :label => "Continue" %>
<% unless #user.first_step? %>
<%= f.commit_button :label => "Back", :button_html => { :name => "back_button" } %>
<% end %>
<% end %>
I added a puts errors message in my code right after the valid? command and it shows as follows:
{:base=>[["first_name", ["cannot be empty"]], ["last_name", ["cannot be empty"]], ["year", ["expired", "is not a valid year"]], ["type", ["is required", "is invalid"]], ["number", ["is not a valid credit card number"]], ["verification_value", ["is required"]], ["address1", ["is required"]], ["city", ["is required"]], ["state", ["is required"]], ["zip", ["is required", "must be a five digit number"]], ["phone", ["is required", "must be in the format of 333-333-3333"]]]}
{:base=>[["first_name", ["cannot be empty"]], ["last_name", ["cannot be empty"]], ["year", ["expired", "is not a valid year"]], ["type", ["is required", "is invalid"]], ["number", ["is not a valid credit card number"]], ["verification_value", ["is required"]], ["address1", ["is required"]], ["city", ["is required"]], ["state", ["is required"]], ["zip", ["is required", "must be a five digit number"]], ["phone", ["is required", "must be in the format of 333-333-3333"]]]}
Now the structure of this output doesn't match the output of a standard error output which is built off a single layer hash such as:
{:username=>["can't be blank"]}
So after showing you all of that, my questions are these:
a) how do I get the error output to show properly so that the form actually spits them out?
b) how do I prevent the parent.valid? from also validating the grandchildren.valid? when I'm not on that page? I can't use the :if => lambda... solution on child models because they don't know what the current_step is.
My apologies for such a long post, I just wanted to include as much information as possible. I've been wrestling with this for a week now and I can't seem to get past it. Any advice would be hugely helpful. Thanks in advance.

The reason the errors aren't showing is probably that they are populated on the base, not on the individual attributes. This happens in your validate_card and validate_address methods. Instead of adding errors to base, you should add them to the specific attribute that caused the error.
errors.add( attr , error )
Secondly, if you want to make your validations dependent on a certain state, as the screencast you mentioned, then you need to save the state flag with the model (probably best the parent). You can do this by hand or, better, you can use a gem for this (recommended): state_machine
Good luck.

On a high level, you seem to be using inheritance in your object modeling and this model is getting built in several forms, in almost 'wizard' like approach. My suggestion would be to model your objects to reflect, the actual forms like,
First part of the form collect basic User information : UserInformation model
Second Part of the form collect payment related information: PaymentInformation model (the Active merchant stuff)
and so on...
Where either the User model has one UserInformation, has one PaymentInformation and so on.
Essentially replace inheritance with Composition. Try and see if you can avoid extending the ActiveMerchant frame work too.
The above style, will give you more control over when you want to call #valid? on a subset of you data model. It get constructed part by part as the user move through the form.
Sorry, I dont have specific solution for you but a more general rewrite approach.

I am new to Ruby-on-Rails and I know this doesn't answer the questions above but you should try out Client-Side Validations and take a look at the Rails-casts. It may be helpful to you!

Related

Memory (hex) address being returned instead of value in Rails

I'm definitely a bit of a noob, so this might be something simple that I'm overlooking, however, the searches that I've done to try and find a solution have come up empty.
I've built a form using formtastic that has 5 input fields: two are text boxes and three are select lists.
<%= semantic_form_for #player do |f| %>
<%= f.inputs do %>
<%= f.input :firstname, :label => "First Name " %>
<%= f.input :lastname, :label => "Last Name " %>
<%= f.input :leagueid, :as => :select, :collection => League.all(:order => :leaguename), :label => "League " %>
<%= f.input :team_1, :as => :select, :collection => Team.all(:order => :name), :label => "Team 1 " %>
<%= f.input :team_2, :as => :select, :collection => Team.all(:order => :name), :label => "Team 1 " %>
<% end %>
<%= f.actions %>
<% end %>
What is happening is that the Teams lists work perfectly (the team names are displayed). However, the League list is a different story. All of the entries in the list look like this (with different a different code after 'League:'):
#<League:0x007fe29c406498>
If I use the form to create a Player, it works fine. The correct league ID goes into the database and everything. I just can't figure out why the names of the teams show, while whatever-that-is shows for the league.
Any and all help is appreciated.
When converting objects to String, Ruby will convert them to the memory address like you see unless you provide a to_s method for string conversions. I haven't used formtastic, but I believe adding a to_s method to your League class should cause it to display what you want.
Try adding
def to_s
#name # use whatever you want to be displayed.
end
to the League class.
You could try explicitly specifying the fields that should be used as the text and id within the select list. I believe it would look like.
<%= f.input :leagueid, :as => :select, :collection => Hash[League.all.map { |league| [league.leaguename, league.id] }]
The syntax is crazy. The call to map is returning an array of name/id pairs, like [ ['league1', 1], ['league2', 2] ]. Calling Hash on that converts it to a hash, like {'league1' => 1, 'league2' => 2}. Seems like the select list should use this hash to populate itself.
There's an example of this at http://rdoc.info/github/justinfrench/formtastic, under the Usage section.
:member_name is the solution I think.
<%= f.input :leagueid, :as => :select, :collection => League.all(:order => :leaguename), :label => "League " %>
Will probably work for you as
<%= f.input :leagueid, :as => :select, :member_name => :league, :collection => League.all(:order => :leaguename), :label => "League " %>
My problem was I have a model with a field the same name as the model and I think that confused Formtastic
Example scaffold:
rails g scaffold Countries code:string country:string
rails g scaffold Types title:string description:string
Model:
class Sign < ActiveRecord::Base
attr_accessible :title, :country_id, :type_id
belongs_to :country
belongs_to :type
Form View:
<%= f.input :type %>
<%= f.input :country, :member_label => :country %>
Having a form without the member_label leads to the object id displaying in the select box for countries although the ID is correctly saved. The type select worked perfectly without declaring member_label.
Note that I didn't need to specify :as => :select as formtastic can deduce this from the belongs_to relationship.
Try using :
<%= form.input :league, :member_label => :leaguename %>
This will override the naming convention of the Formtastic Column Select.
Have a look also at : Overriding the Column Name Convention wiki
Hope this helped.

Routing issue with Devise confirmable - Email only signup, getting error "Couldn't find User with id=confirmation"

I think I have a routing issue with Devise that I can't figure out. I am trying to implement this method for having users sign up using Devise for authentication. The example uses haml, and I have attempted to translate that back to erb, hopefully with some success.
Rails 3.2.8 in development on Mac OS X, using PostgresQL
Using Devise and simple_form
app/views/devise/registrations/new.html.erb:
<%= simple_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%# simple_form_for([:backend, #user]) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.input :email, :required => true, :autofocus => true, :label => "Email" %>
</div>
<div class="actions">
<%= f.button :submit, "Add User" %>
</div>
<% end %>
app/views/devise/confirmations/show.html.erb:
<%= simple_form_for(resource, :as => resource_name, :url => confirmation_path(resource_name)) do |f| %>
<div class="inputs">
<%= f.input :password, :required => true, :label => "Password" %>
<%= f.input :password_confirmation, :required => true, :label => "Confirm Password" %>
<%= f.hidden_field :confirmation_token %>
</div>
<div class="actions">
<%= f.button :submit, "Confirm" %>
</div>
<% end %>
app/modesl/user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable,
:timeoutable
# Setup accessible (or protected) attributes for your model
attr_accessible :email,
:password,
:password_confirmation,
:remember_me,
:username
def password_required?
super if confirmed?
end
def password_match?
self.errors[:password] << "can't be blank" if password.blank?
self.errors[:password_confirmation] << "can't be blank" if password_confirmation.blank?
self.errors[:password_confirmation] << "does not match password" if password != password_confirmation
password == password_confirmation && !password.blank?
end
end
app/controllers/confirmations_controller.rb:
class ConfirmationsController < Devise::ConfirmationsController
def show
self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
super if resource.confirmed?
end
def confirm
self.resource = resource_class.find_by_confirmation_token(params[resource_name][:confirmation_token])
if resource.update_attributes(params[resource_name].except(:confirmation_token)) && resource.password_match?
self.resource = resource_class.confirm_by_token(params[resource_name][:confirmation_token])
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, resource)
else
render :action => "show"
end
end
end
app/config/routes.rb:
devise_for :users, :controllers => {:confirmations => 'confirmations'}
devise_scope :user do
put "/confirm" => "confirmations#confirm"
end
resources :users
When I input my email address at the users/sign_up page, everything works fine. I receive the confirmation email, click the confirmation link and am directed to:
/users/confirmation?confirmation_token=[CONFIRMATION TOKEN HERE]
Then, when I fill in my password and submit, I am directed to this page:
users/confirmation
with the following error:
ActiveRecord::RecordNotFound in UsersController#update
Couldn't find User with id=confirmation
I'm pretty sure it's a routing issue. Any ideas? Thanks so much!
Cyle, thanks for getting my brain working this evening. The answer was staring me right in the face the whole time. In routes, I changed
match "/confirm" => "confirmations#confirm"
to
match "/confirmation" => "confirmations#confirm"
Now I can start researching the next error it throws. Thanks again!

Rails: Validation for a simple_form using has_many relationship (e.g. Person, Phone)

I'm struggling getting the desired validation with nested models within a simple_form. You'll be able to see from the models below a Person can have many Phone's, the desired behaviour is to present edit fields for any existing numbers plus an additional one should for a new number, if this new number isn't filled in by the user then it's just ignore and not saved in the database. I also want to achieve similar with Email.
When landing on the /people/:id/edit page this blank field is being prematurely validated and producing visible errors on the form before submitting. It doesn't do this when visiting /people/:id/new page; I'm assuming that this is because new_record? returns true for the user model on the new page? In reading a similar post I added on: :save as a parameter to validates on the Phone model although this just allowed blank records into the database, perhaps because this isn't relevant when the user model is saving the record?
class Person < ActiveRecord::Base
belongs_to :company
has_many :phones, :as => :phoneable
has_many :emails, :as => :emailable
has_many :addresses, :as => :addressable
attr_accessible :first_name, :job_title, :last_name, :prefix, :phones_attributes, :emails_attributes, :addresses_attributes, :company_id
accepts_nested_attributes_for :phones, allow_destroy: true, reject_if: proc { |attributes| attributes['number'].blank? }
accepts_nested_attributes_for :emails, allow_destroy: true, reject_if: proc { |attributes| attributes['email'].blank? }
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
validates :first_name, :last_name, presence: true
def to_s
"#{first_name} #{last_name}"
end
end
class Phone < ActiveRecord::Base
belongs_to :phoneable, polymorphic: true
attr_accessible :number, :phone_type
validates :number, :phone_type, presence: true, on: :save # as suggested in a similar post, just allows blank records into database.
def to_s
"#{phone_type}: #{number}"
end
end
With both the new and edit controller I'm creating a new instance of each of these models so that they show up on the form. #person is loaded in the controller using load_and_authorize_resource as part of cancan.
def new
#person.phones << Phone.new
#person.emails << Email.new
end
Here is the partial view for the form:
<%= simple_form_for #person, :html => { :class => 'form-horizontal' } do |f| %>
<fieldset id="<%= controller.action_name.capitalize %>_person">
<legend><%= controller.action_name.capitalize %> Person</legend>
<%= f.input :prefix %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.input :job_title %>
<%= f.association :company, :prompt => "Select associated company..." %>
<%= f.simple_fields_for :phones do |phone| %>
<%= phone.input :phone_type, :collection => %w(Work Home Mobile Fax Other), :default => "Work" %>
<%= phone.input :number %>
<% end %>
<%= f.simple_fields_for :emails do |email| %>
<%= email.input :email_type, :collection => %w(Work Home Other), :default => "Work" %>
<%= email.input :email %>
<% end %>
<div class="form-actions">
<%= f.submit nil, :class => 'btn btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
people_path, :class => 'btn' %>
</div>
</fieldset>
<% end %>
Many thanks for any help in advance :-)

user and group join model validation

I have the models User and Group, and the join table Membership which uses has_many :through method. In my join table's form, I want the user to input a valid group name that an administrator has created to become a member of that group.
I have gotten the case where a valid name is entered to work but now I need some validation if they input a blank text box, or that the inputted group name exists in the database, I'd like some nice error messages. I thought this would be possible through some validates method?
membership partial _form.html.erb
<%= form_for(#membership) do |f| %>
<% if #membership.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#membership.errors.count, "error") %> prohibited this membership from being saved:</h2>
<ul>
<% #membership.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :group %><br />
<%= f.text_field :group %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
membership.rb
class Membership < ActiveRecord::Base
belongs_to :group
belongs_to :user
attr_accessible :user_id, :group_id
validates_uniqueness_of :group_id, :message => "can be only joined once", :scope => 'user_id'
validates_presence_of :group, :user
end
group.rb
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :users, :through => :subscriptions
validates :name, :presence => true, :uniqueness => true
attr_accessible :name, :expiry
end
So need some direction as how the validation happens because the above validation in the membership and group models doesn't work, I get the error for both empty text box or name not in the database...
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Edit: Added controller code below
def create
#group = Group.find_by_name(params[:membership][:group])
#membership = current_user.memberships.build(:group_id => #group.id)
respond_to do |format|
if #membership.save
format.html { redirect_to membership_url, :notice => 'Membership was successfully created.' }
format.json { render :json => #membership, :status => :created, :location => #membership }
else
format.html { render :action => "new" }
format.json { render :json=> #membership.errors, :status => :unprocessable_entity }
end
end
end
Add the following to your group model:
validate_uniqueness_of :name, :message => "a group already exists with that name"
validate_presence_of :name
The validations are called for all tables you are inserting to.
Edit:
Change your controller like this:
#membership = current_user.memberships.build(:group => #group)
You won't get the id called on nil error anymore. And if #group is nil, the validation in your membership will pick it up on the save attempt.
I solved this question by changing the text input box to a select field, and will add a password field to the model to solve my issue of unwanted users joining the group.
def create
#groups = Group.current
#group = Group.find_by_name(params[:membership][:group])
#membership = current_user.memberships.build(:group_id => #group.id)
After which my model validation works now below
validates_uniqueness_of :group_id, :message => "can be only be joined once", :scope => 'user_id'

Can't mass-assign protected attributes:

I have pulled out all my hair. No more left... :(
I am using Spree 0.3.4, within an extension I need to register some retailers up. so I direct them to a retailers form which has many custom fields which belong to a retailer model...
So I am trying to validate/submit all the fields from one form like so
myextension/app/views/user_registrations/new.html.erb
<%= form_for (:user, :url => registration_path(#user, :type => "retailer) do |f| %>
<%= f.fields_for :retailer do |r| %>
<%= r.text_field :name %>
<% end %>
<%= f.text_field :email %>
<% end %>
etc etc
class Retailer < ActiveRecord::Base
belongs_to :user
validates :name,
:presence => true
end
class User < ActiveRecord::Base
has_one :retailer
accepts_nested_attributes_for :retailer
attr_accessible :retailer_attributes
# theres a whole lot more spree and devise stuff here. not sure worth mentioning
end
I have also added the abilities in the cancan ability.rb
The problem is the retailer feilds never get validated and the data is never inserted into the database...
I created a blank app, and tried this process from scratch with some plain old scaffolding and it works fine.
any ideas??
In your application helper, do something like this(assuming your have Ruby 1.9.* for the tap functionality, otherwise checkout rails returning here):
def setup_user(user)
user.tap do |u|
u.build_retailer if u.retailer.nil?
end
end
then in your view change it to this:
<%= form_for (setup_user(#user), :url => registration_path(#user, :type => "retailer) do |f| %>
See if that works.