Ecommerce shipping and billing address into one table in rails - ruby-on-rails

I'm trying to create an address form with shipping and billing address on same page.
When user gets ready for checkout , I want both shipping address form and billing address for to appear on same page. If billing address same as shipping address only record should be inserted into address table , if different two records has to be inserted and of course an update has to take place in orders table shipping_address_id,billing_address_id.
Having only one address model, how do I achieve two forms with one submit button.
Below is my model for address and orders
I need some help in putting in controller also I'm trying to get a hash value for each billing and shipping
Please help!!!
class Address < ActiveRecord::Base
attr_accessible :name,:first_name,:last_name,:address1,:address2,:city,:state,:zip,:phone,:billing_default,: user_id,:billing_address, :shipping_address
belongs_to :user
has_many :billing_addresses, :class_name => "Order", :foreign_key => "billing_address_id"
has_many :shipping_addresses, :class_name => "Order", :foreign_key => "shipping_address_id"
class Order < ActiveRecord::Base
attr_accessible :cart_id, :order_no, :sales_tax, :shipping_fee,:total,:order_state,:gateway_type,:transaction_id,:transaction_status,:ip_address,:card_verification,:card_number,:billing_address_id,:shippin g_address_id,:first_name,:last_name,:user_id,:card_expires_on,:authenticity_token
belongs_to :cart
belongs_to :user
belongs_to :billing_address, :class_name => "Address"
belongs_to :shipping_address, :class_name => "Address"
attr_accessor :card_number
has_many :transactions, :through => :order_id
has_many :invoices
has_many :order_details

This is a slightly complicated problem, you will find.
First, ask yourself: Do you really only want to insert one address if billing and shipping addresses are the same?
A customer wants to change the shipping address. You will need logic to create another address record and retain the original as billing.
Generally, avoid updates to billing and shipping addresses after an order is complete as they compromise data integrity. Once an order is closed, that's it; those addresses should be fixed. When an order requires a different shipping address, avoid having a dependency between it and the billing address.
Now, assuming you're going ahead.
Using Nested Forms
Hide billing fields, and add a check box to your form that maps to an order.bill_to_shipping_address. Default it to checked. Show billing address if it gets unchecked.
$('input[name="order[bill_to_shipping_address]"]').on 'click', ->
if $(this).is ':checked'
$('fieldset.billing_fields').hide()
else
$('fieldset.billing_fields').show()
In your order model:
accepts_nested_attributes_for :shipping_address
accepts_nested_attributes_for :billing_address, reject_if: :bill_to_shipping_address
The draw back with this approach is, if there is a validation error, and the user happens to change his mind and bill to a different address, the billing form will not appear since it gets rejected.
Use a Form Object
This might seem more complex, but it's a much cleaner solution.
See 7 Patterns for refactoring ActiveRecord Objects.
Build a form object as such. I've adopted this code from something I recently wrote for a Rails 4 app. Just reverse your relationships. In my case an order has one billing address and one shipping address; it does not belong to them.
class OrderForm
include ActiveModel::Model
def self.model_name
ActiveModel::Name.new(self, nil, "Order")
end
def persisted?
false
end
attr_accessor :params
delegate :email, :bill_to_shipping_address, to: :order
# Removed most fields for brevity
delegate :name, :street, :street_number, to: :shipping_address, prefix: :shipping
delegate :name, :street, :street_number, to: :billing_address, prefix: :billing
# Removed most fields for brevity
validates :email, length: { maximum: 60 }, email_format: true
validates :shipping_name, :shipping_street, presence: true
validates :billing_name, presence: true, unless: -> { bill_to_shipping_address }
def initialize(params = nil)
#params = params
end
def submit
populate
if valid?
order.save!
true
else
false
end
end
def order
#order ||= Order.new
end
private
def shipping_address
#shipping_address ||= order.build_shipping_address
end
def billing_address
#billing_address ||= order.build_billing_address
end
def populate
order.email = params[:email]
order.bill_to_shipping_address = params[:bill_to_shipping_address]
shipping_address.name = params[:shipping_name]
# etc...
unless order.bill_to_shipping_address?
billing_address.name = params[:billing_name]
# etc...
end
end
end
Then from the controller:
def new
#order_form = OrderForm.new
end
def create
#order_form = OrderForm.new(params[:order])
if #order_form.submit
# order saved, do whatever
else
render 'new'
end
end
Your form now does not care about nested attributes and properties. It's nice a clean.
= form_for #order do |f|
= f.text_field :email
= f.text_field :shipping_street
= f.text_field :billing_street
# etc...

I'd suggest using a checkbox so the user can specify whether use the same billing and shipping address or type different ones.
In the form file you need to handle nested forms in the following way:
= form_for #order do f
= f.fields_for :billing_address do |ba|
= ba.text_field :address1
= ba.text_field:address2
= ba.text_field :city
= ba.text_field :state
= ba.text_field :zip
= ba.text_field :phone
= f.fields_for :shipping_address do |sa|
= sa.text_field :address1
= sa.text_field:address2
= sa.text_field :city
= sa.text_field :state
= sa.text_field :zip
= sa.text_field :phone
In your model don't forget to add:
accepts_nested_attributes_for :shipping_address
accepts_nested_attributes_for :billing_address
And probably need to add the autobuild to your address relations
belongs_to :billing_address, :class_name => "Address", autobuild: true
belongs_to :shipping_address, :class_name => "Address", autobuild: true
In the controller create/update actions, you just need to check the checkbox value and assign them equal, here's one approach:
#order.shipping_address = #order.billing_address if params[:checkbox_use_same_address] == true

Related

Ruby on Rails has_and_belongs_to_many uninitialized constant

I'm building a site on Refinery CMS, and have generated two extensions: one for Brands, and another for Bicycle Types (it's a site for a bike shop).
Now, what I want to do is have the Brands extension handle the creation of brand pages, which will be pulled into a brand index. On this page, I want to be able to filter by Bicycle Type, which is where the second extension comes in. Through the Bicycle Type extension, you can create a bicycle type, which I want to associate to a Brand. A Brand can have multiple Bicycle Types, and vice versa.
So, I edited the Brands model to add has_and_belongs_to_many :bicycle_types, and the Bicycle Types model to include has_and_belongs_to_many :brands and accepts_nested_attributes_for :brands. I wrote a migration to create a join table, and everything was going well so far.
I then went to modify the form for the Brands extension, and got my checkboxes displaying correctly and seemingly generating the right code. However, the problem occurs when I come to submit the form - I get NameError in Refinery::Brands::Admin::BrandsController#update and uninitialized constant Refinery::Brands::Brand::BicycleType.
The parameters I get look like the bicycle type IDs are being passed through correctly:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"3193ZMPXkmHdgZThXwAurD6xF2eZ533Tb71pAi7Jxbs=",
"switch_locale"=>"en",
"brand"=>{"title"=>"Cannondale",
"teaser"=>"",
"splash"=>"",
"details"=>"",
"introduction"=>"",
"blockquote"=>"",
"bicycle_type_ids"=>["1",
"2"],
"logo_id"=>"",
"teaser_image_id"=>"",
"splash_image_id"=>""},
"id"=>"2",
"locale"=>:en}
I've been trying to figure this out and just keep hitting the same brick wall, so any help would be greatly appreciated!
Here's my code. Let me know if anything else would help.
Brands Controller
module Refinery
module Brands
module Admin
class BrandsController < ::Refinery::AdminController
crudify :'refinery/brands/brand',
:xhr_paging => true
end
end
end
end
Brands Model
module Refinery
module Brands
class Brand < Refinery::Core::BaseModel
self.table_name = 'refinery_brands'
attr_accessible :title, :teaser, :splash, :details, :introduction, :blockquote, :logo_id, :teaser_image_id, :splash_image_id, :position, :bicycle_type_ids
translates :title, :teaser, :splash, :details, :introduction, :blockquote
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
belongs_to :logo, :class_name => '::Refinery::Image'
belongs_to :teaser_image, :class_name => '::Refinery::Image'
belongs_to :splash_image, :class_name => '::Refinery::Image'
has_and_belongs_to_many :bicycle_types
end
end
end
Bicycle Types Model
module Refinery
module BicycleTypes
class BicycleType < Refinery::Core::BaseModel
self.table_name = 'refinery_bicycle_types'
attr_accessible :title, :position
translates :title
class Translation
attr_accessible :locale
end
validates :title, :presence => true, :uniqueness => true
has_and_belongs_to_many :brands
accepts_nested_attributes_for :brands
end
end
end
Migration
class AddRefineryBicycleTypesBrands < ActiveRecord::Migration
def change
create_table :bicycle_types_brands, :id => false do |t|
t.references :bicycle_type
t.references :brand
end
add_index :bicycle_types_brands, [:bicycle_type_id, :brand_id], :unique => true
end
end
Form Partial (at least the part where I'm building my checkboxes)
<div class="field">
<%= f.label :bicycle_types %>
<% Refinery::BicycleTypes::BicycleType.order(:title).each do |bicycle_type| %>
<label class="checkbox">
<%= check_box_tag "#{f.object_name}[bicycle_type_ids][]", bicycle_type.id, f.object.bicycle_types %>
<%= bicycle_type.title %>
</label>
<% end %>
</div>
If the rest of the partial would be useful, or anything else for that matter, please let me know. Any help would be greatly appreciated!
you must specify the full class name:
has_and_belongs_to_many :bicycle_types, :class_name => "Refinery::BicycleTypes::BicycleType"
This is not your case but if you want to call the join table in a 'refinery style' (i.e refinery_bicycle_types_brands), you must also declare the join table:
has_and_belongs_to_many :bicycle_types, :join_table => :refinery_bicycle_types_brands, :class_name => "Refinery::BicycleTypes::BicycleType"
Bye

How to search in has_many association using Sunspot/Solr?

Here is what my initial searchable block looks like in my User model:
searchable do
text :name
integer :sport_ids, multiple: true do
sports.map(&:id)
end
integer :position_ids, multiple: true do
positions.map(&:id)
end
integer :city_id
integer :school_id
string :state
end
How do I search by has_many associations? I need it to return each Athlete who has a specified ID in their sports or sport_positions. So if someone selects "Basketball" from a dropdown, ID of 2 is passed to my search method and it needs to return Athletes who have sport_id of 2 in their collection of sport_ids. Here is how sports and sport_positions are declared in the User model:
has_and_belongs_to_many :positions, :class_name => "SportPosition", :join_table => "user_sport_positions", :uniq => true
has_many :sports, :through => :user_sports, order: "user_sports.created_at", class_name: "Sport"
has_many :user_sports
:::EDIT:::
This worked for a minute after I reindexed, then all of a sudden I started getting this error:
Sunspot::UnrecognizedFieldError (No field configured for Athlete with name 'sport_ids'):
app/models/search.rb:12:in `block in execute'
here is my Search model:
class Search < ActiveRecord::Base
attr_accessible :coach_id, :sport_id, :gpa_min, :gpa_max, :sport_position_id,
:classification, :city_id, :state, :school_id, :athlete_name
belongs_to :coach
def self.execute(params, page = nil)
Sunspot.search(Athlete) do |query|
query.with :public, true
query.with :removed_from_listing, false
query.fulltext params[:athlete_name] unless params[:athlete_name].blank?
query.with :sport_ids, params[:sport_id] unless params[:sport_id].blank?
query.with :position_ids, params[:sport_position_id] unless params[:sport_position_id].blank?
query.with(:state).equal_to(params[:state]) unless params[:state].blank?
query.with(:classification).equal_to(params[:classification]) unless params[:classification].blank?
query.with :city_id, params[:city_id] unless params[:city_id].blank?
query.with :school_id, params[:school_id] unless params[:school_id].blank?
query.with(:current_gpa).between(params[:gpa_min]..params[:gpa_max]) unless params[:gpa_min].eql?("0.0") && params[:gpa_max].eql?("5.0")
query.paginate page: page unless page.blank?
end
end
end
NOTE: To make this even more strange, I have a field called "recruit_year" that is an integer attribute. I was getting the same error on this field saying "No field configured" blah blah. That error usually only happens on text fields if you try to do a comparison like equal_to or treat it like a string.
Help?
This works fine, the problem was STI. I had an Athlete block that was overriding the User block.

How to save embedded classes in mongoid?

I am using Rails 3 with mongoid 2. I have a mongoid class forum, which embeds_many topics.
Topics embeds_many forumposts
When I try to save a forumpost doing the following in my controller...
#forum = Forum.find(params[:forum_id])
#forum.topics.find(params[:topic_id]).forumposts.build(:topic_id => params[:forumpost][:topic_id], :content => params[:forumpost][:content], :user_id => current_user.id,:posted_at => Time.now, :created_at => Time.now, :updated_at => Time.now)
if #forum.save
On save I get...
undefined method `each' for 2012-11-14 23:15:39 UTC:Time
Why am I getting that error?
My forumpost class is as follows...
class Forumpost
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
field :content, type: String
field :topic_id, type: String
field :user_id, type: String
field :posted_at, type: DateTime
attr_accessible :content, :topic_id, :user_id, :posted_at, :created_at, :updated_at
validates :content, presence: true
validates :topic_id, presence: true
validates :user_id, presence: true
belongs_to :topic
belongs_to :user
end
There is alot wrong/wierd with your example code, so lets see if we can start at the start:
You say forum embeds many topics, which embeds many posts. But your model is using a belongs_to association. Belongs_to is used for references which are different than embedded documents. If your Topic model has this:
class Topic
...
embeds_many :forumposts
...
end
Then your Forumpost model should have this:
class Forumpost
...
embedded_in :topic
...
end
Read up on references vs embedded documents here: http://mongoid.org/en/mongoid/docs/relations.html
Next point, You don't need to put :topic_id into the forumpost since you are building it off the topic.
Next point, don't save the forum, save the forumpost. And instead of doing a build followed by a save, try just doing it as a create in one go.
Next point, instead of setting user_id => current_user.id, try setting user => current_user. This is the magic that the belongs_to association provides... its cleaner and avoids messing around with IDs.
Next point, why do you need both created_at (supplied by Mongoid::Timestamps) and posted_at ?
Last point, you shouldn't need to set the timestamps, they should be set automatically when created/updated (unless for some reason you actually need posted_at).
Try something more like this:
#forum = Forum.find(params[:forum_id])
#topic = #forum.topics.find(params[:topic_id])
if #topic.forumposts.create(:content => params[:forumpost][:content], :user => current_user)
#handle the success case
else
#handle the error case
end

How do you write a Rails model method to return a list of its associations?

What I'm trying to do is write a method that will return all of this model's outing_locations, to which it has a has_many relationship.
class Outing < ActiveRecord::Base
attr_accessible :description, :end_time, :start_time, :title, :user_id
belongs_to :user
has_many :outing_locations
has_many :outing_guests
has_one :time_range, :foreign_key => "element_id", :conditions => { :element_type => "outing" }
validates :title, :presence => true
validates :start_time, :presence => true # regex
validates :end_time, :presence => true # regex
def host_name
return User.find(self.user_id).full_name
end
end
I'm trying to get this block in particular to work.
There's another model called OutingInvite, which contains the id of a particular Outing. I need to use that to grab the proper Outing and then pull said Outing's associated outing locations.
Here's a rough sample:
<%#outing_invites.each do |invite|%>
...
<% #party = Outing.where(:id => invite.outing_id) %>
<% #party.outing_locations.each do |location| %>
And then have it output each location.
However, it's saying the method 'outing_locations' does not exist...
You can see a model's associated models by typing model_instance.associated_model_name. So in your example, an outing has_many outing_locations. After you have an instance of an outing, say by using #o = Outing.find(1), you can then use o.outing_locations to see the outing_locations associated with that specific outing.
See this example from the Ruby on Rails Guide.
EDIT
The reasons you're getting the method 'outing_locations' does not exist error is because Outing.where(:id => invite.outing_id) returns an array, and there is no outing_locations method for arrays. You'll need to either get the specific instance (like with Outing.find(invite.outing_id) or use a specific index in that array. I recommend using Outing.find(invite.outing_id) since (I'm assuming) each of your Outing's has a unique id.

Why does associated object not save?

I have a ruby (on rails) class:
class User < ActiveRecord::Base
# relationships
belongs_to :current_shipping_address, :class_name => "Address"
belongs_to :subscription
# Validators
validates_presence_of :subscription
validates_presence_of :current_shipping_address
end
I do this in a controller:
subscription = Subscription.new
address_info = params[:user].delete(:address) rescue {}
#address = Address.new(address_info.merge(:country => "US"))
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.subscription = subscription
#user.current_shipping_address = #address
#user.save!
At this point, incredibly, I have a #user that has been saved in the database but has no current_shipping_address (despite the validation). The subscription has also been saved in the database.
The address does NOT get saved.
What am I missing here?
1 - how does user get saved without the validation failing?
2 - why is the address not saved?
How can I alter this code so that the address gets saved (as I would expect it to)?
I am running this under ruby on rails 3.
Thanks!
You cannot have subscription and current_shipping_address saved by user in your case because, they are not simple fields in model User. You define them as model associated to User through belongs_to, I'm not sure about what you are willing to do, but if I understand correctly one way to do it is using nested attributes:
class User < ActiveRecord::Base
# relationships
has_many :current_shipping_addresses, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
# Nesting
accepts_nested_attributes_for :subscriptions
accepts_nested_attributes_for :current_shipping_addresses
end
After that, when you then create and save a User, a subscription and current_shipping_address are saved whith it .
More on assocations here : http://guides.rubyonrails.org/association_basics.html
You need to tell it what the foreign key is if you're not sticking with the standard table structure. You'll just need to add:
belongs_to :current_shipping_address, :class_name => "Address", :foreign_key => "address_id"
or whatever column you are using to store the address id to the address table.
This is not the recommended way of doing nested attributes though. I would recommend using a fields_for in your form rather than using the lines:
address_info = params[:user].delete(:address) rescue {}
#address = Address.new(address_info.merge(:country => "US"))
You can just do
<%= f.fields_for :current_shipping_address do |ff| %>
# ... your address fields...
<% end %>
which will then let you simply save the address when you run #user.save!
You can still add the :country => "US" beforehand with
params[:user][:current_shipping_address][:country] = "US"
and then run save. Its really up to you though.
Try this way!
subscription = Subscription.new
address_info = params[:user].delete(:address) rescue {}
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.subscription = subscription
#user.current_shipping_address << Address.new(address_info.merge(:country => "US"))
#user.save!
Seems the problem was that address was actually failing to save. not because of a validation but because of an error in a 'before_create' method (and yes I know I didn't give you the address object... I didn't think it important at the time!).
class Address < ActiveRecord::Base
# relationships
# Validators
validates_presence_of :city, :state, :country, :first_name, :last_name, :address_1
before_create :check_state
before_create :check_country
def check_state
retval = true
state.upcase!
if country == "US" and !US_STATES.map{|s| s[1]}.include?(state)
errors.add(:state, "Must be valid")
retval = false
end
retval
end
end
Check state was failing. But that meant that address passed the 'valid?' call, which it seems is all active record cares about. (This method really should be a validation)
I have switched to doing this (thanks enokd for the link!):
#user = User.new(params[:user].merge(:first_name => #address.first_name, :last_name => #address.last_name))
#user.build_subscription(:subscription_plan_id => #subscription_plan.id)
#user.build_current_shipping_address(address_info.merge(:country => "US"))
I haven't bothered to investigate fully, but, if address fails to save it stops the whole #user.save!
Personally I think this is a little bit of bug perhaps or certainly an unexpected behaviour, but what do I know!
Try:
class User < ActiveRecord::Base
# relationships
has_one :current_shipping_address, :class_name => "Address", :dependant => destroy
has_many :subscriptions, :dependant => destroy
validates :current_shipping_address, :presence => true
end

Resources