Rails won't create instance variable - sql

Ok so I am building an eBay clone of sorts and I am trying to implement Braintree as the payments processor and I am having serious issues with my orders controller. All of my logic for creating customers in braintree etc. works. The issue I am having is that I can't get the product id to save when creating my orders.
I have nested the routes for my orders resource below my products resource like so:
resources :products do
resources :orders
end
Due to the routing I have set up, the parameters that are passed are:
{"_method"=>"get", "authenticity_token"=>"", "controller"=>"orders", "action"=>"new", **"product_id"=>"4"}**
My product ID gets passed into the new action. My logic is that I can store it in an instance variable and then save it to my order. My controller is as follows:
def new
if current_user.is_customer?
#client_token = Braintree::ClientToken.generate(:customer_id => current_user.id)
else
#client_token = Braintree::ClientToken.generate()
end
console
end
def create
amount = 50 # only set to a whole decimal number with no '.'
amount_str = "#{amount}.00"
#product=Product.find_by(params[:product_id])
nonce = params[:payment_method_nonce]
render action: :new and return unless nonce
#order = Order.new
#order.amount = amount
#order.user_id = current_user.id
#order.product_id= #product.id
if current_user.is_customer?
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer_id => current_user.id,
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
},
)
if result.success?
#order.save!
redirect_to support_product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
else
result = Braintree::Transaction.sale(
:amount => amount_str,
:payment_method_nonce => nonce,
:order_id => #order.id,
:customer => {
:id => current_user.id,
:first_name => current_user.first_name,
:last_name => current_user.last_name
},
:options => {
:submit_for_settlement => false,
:store_in_vault_on_success => true,
}
)
if result.success?
#order.save!
redirect_to product_path(#product), notice: "Success. Orange you glad I've used Braintree to process your payment?!"
else
render 'new'
flash[:alert] = "Something wasn't quite right. #{result.transaction.processor_response_text}"
end
end
end
I have an association for orders and products and I have done a references migration like so:
class AddProductsUsersToOrders < ActiveRecord::Migration
def change
add_reference :orders, :product, index: true
add_foreign_key :orders, :products
add_reference :orders, :user, index: true
add_foreign_key :orders, :users
end
end
Order Model:
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
So the logic works and gets created however it saves the product_id as the first product in my database and redirects to it regardless of which product I try to order. I have spent hours on this any help is appreciated.

Related

Active admin and money

I am using active admin with the money - https://github.com/RubyMoney/money gem. I have some attributes handled by the money gem.
The money gem stores values in cents. When i create an entry with active admin, the correct value is created in DB (5000 for 50.00).
However when i edit an entry, the value is multiplied by 100, meaning that AA display 5000 for an original input of 50.00. If i edit anything with a money attribute, it will multiply by 100. At creation, the value goes through money logic, but at edition, somehow active admin skip that part displaying cents instead of the final monetary value.
Is there a way to use the money gem with active admin?
example :
form :html => { :enctype => "multipart/form-data"} do |f|
f.inputs "Products" do
......
f.has_many :pricings do |p|
p.input :price
p.input :_destroy, :as => :boolean,:label=>"Effacer"
end
f.actions :publish
end
Model :
# encoding: utf-8
class Pricing < ActiveRecord::Base
belongs_to :priceable, :polymorphic => true
attr_accessible :price
composed_of :price,
:class_name => "Money",
:mapping => [%w(price cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
end
Rails callbacks are very handy for creating a solution to this kind of issue.
I would just use and after_update callback.
Example:
# encoding: utf-8
class Pricing < ActiveRecord::Base
after_update :fix_price
belongs_to :priceable, :polymorphic => true
attr_accessible :price
composed_of :price,
:class_name => "Money",
:mapping => [%w(price cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
def fix_price
self.price = (self.price/100)
end
end
My problem came from my usage of Money :
composed_of :price,
:class_name => "Money",
:mapping => [%w(price_cents cents), %w(currency currency_as_string)],
:constructor => Proc.new { |price_cents, currency| Money.new(price_cents || 0, currency || Money.default_currency) },
:converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
I renamed my price in DB by price_cents, and i put it in the class where it was needed in the money declaration. I was using cent where i should have used price, and even then having the money object and the field in DB using the same name doesn't seem to work. In the end the problem was not related to Active Admin.

Rspec factory_girl ActiveMerchant undefined method `credit_card='

I'm attempting to test the creation of an Reservation model that involves processing a payment with ActiveMerchant upon creation.
The initial setup for payment processing involved following the ActiveMerchant Railscasts. Payments are working fine within the app. (http://railscasts.com/episodes/145-integrating-active-merchant)
I've tried creating the credit_card object within the Reservation factory and within it's own ":valid_credit_card" factory...
The basic test is just attempting to verify the Reservation can be created.
Test results:
1) Reservation should have a valid factory
Failure/Error: #current_reservation = Factory.create(:reservation)
NoMethodError:
undefined method `credit_card=' for #<Reservation:0xb5f6173c>
# ./spec/models/reservation_spec.rb:11
The Reservation belongs_to a user and has_many rooms through reservation_sets
Factory.define :reservation do |f|
f.association :user
f.rooms { |a| [a.association(:room)] }
f.arrival Time.now + 2.weeks
f.nights 2
f.phone "555-123-1234"
f.credit_card :valid_credit_card
end
Factory.define :valid_credit_card, :class => ActiveMerchant::Billing::CreditCard do |f|
expiration_date = Time.zone.now + 1.year
f.type "visa"
f.number "4111111111111111"
f.verification_value "333"
f.month expiration_date.strftime("%m")
f.year expiration_date.strftime("%y")
f.first_name "Bob"
f.last_name "Smith"
end
And the spec/models/reservation_spec.rb. Using #credit_card Factory.build causes errors about "saving" the credit_card.
If I remove the line f.credit_card :valid_credit_card I get the NoMethodError for :month
even though :month is listed in attr_accessor. The creation of a reservation within the app does work.
1) Reservation should have a valid factory
Failure/Error: #current_reservation = Factory.create(:reservation)
NoMethodError:
undefined method `month' for nil:NilClass
describe Reservation do
before :each do
#smith = Factory.create(:user)
#room = Factory.create(:room)
##credit_card = Factory.build(:valid_credit_card)
end
it "should have a valid factory" do
#current_reservation = Factory.create(:reservation)
#current_reservation.should be_valid
end
end
What am I overlooking / doing incorrectly...?
Reservation model excerpts
class Reservation < ActiveRecord::Base
# relationships
belongs_to :user
has_many :reservation_sets,
:dependent => :destroy
has_many :rooms,
:through => :reservation_sets
has_many :transactions,
:class_name => 'ReservationTransaction',
:dependent => :destroy
attr_accessor :card_number, :card_verification, :card_expires_on, :card_type, :ip_address, :rtype, :month, :year
# other standard validations
validate :validate_card, :on => :create
# other reservation methods...
# gets paid upon reservation creation
def pay_deposit
# Generate active merchant object
ReservationTransaction.gateway =
ActiveMerchant::Billing::AuthorizeNetGateway.new({
:login => rooms[0].user.gateway_login,
:password => rooms[0].user.gateway_password
})
response = ReservationTransaction.gateway.purchase(deposit_price_in_cents, credit_card, purchase_options)
t = transactions.create!(:action => "purchase", :amount => deposit_price_in_cents, :response => response)
if response.success?
update_attribute(:reserved_at, Time.now)
# update state
payment_captured!
else
transaction_declined!
errors.add :base, response.message
end
t.card_number = credit_card.display_number
t.save!
response.success?
end
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
errors.add :base, message #_to_base message
end
end
end
def credit_card
#credit_card ||= ActiveMerchant::Billing::CreditCard.new(
:type => card_type,
:number => card_number,
:verification_value => card_verification,
:month => card_expires_on.month,
:year => card_expires_on.year,
:first_name => first_name,
:last_name => last_name
)
end
And the create action from the Reservation Controller
def create
#reservation = Reservation.new(params[:reservation])
#reservation.arrival = session[:arrival]
#reservation.nights = session[:nights]
#reservation.number_kids = session[:number_kids]
#reservation.number_adults = session[:number_adults]
session[:creating_reservation] = 1
#reservation.user_id = #reservation.rooms[0].user_id
session[:owner] = #reservation.user_id
#rooms = Room.all
#reservation.ip_address = request.remote_ip
# get room owner...
#owner = User.find(#reservation.user_id)
respond_to do |format|
if #reservation.save
if #reservation.pay_deposit
#set cc...
#reservation.transactions[0].card_number = #reservation.send(:credit_card).display_number
ReservationMailer.reservation_created(#reservation).deliver
ReservationMailer.reservation_notice(#reservation).deliver
session[:arrival] = nil
session[:reservation_id] = #reservation.id
if #owner
thanks_path = "#{#owner.permalink}/reservations/#{#reservation.id}"
else
thanks_path = #reservation
end
format.html { redirect_to #reservation, :notice => 'Reservation was successfully created.' }
format.json { render :json => #reservation, :status => :created, :location => #reservation }
# also trigger email sending or wherever that is
# receipt email and order notification
#
else
# set flash or show message problem w/ transaction
format.html { render :action => "new" }
end
else
format.html { render :action => "new" }
format.json { render :json => #reservation.errors, :status => :unprocessable_entity }
end
end
end
It looks like you are trying to assign credit_card a value, but you don't really have a class accessor. So where you are trying to call f.credit_card :valid_credit_card isn't going to work.
I would remove the f.credit_card :valid_credit_card from your factory and look into using rspec stubs, then you could do something like the following in your rspec test:
mock_cc = ActiveMerchant::Billing::CreditCard.new(
:type => card_type,
:number => card_number,
:verification_value => card_verification,
:month => card_expires_on.month,
:year => card_expires_on.year,
:first_name => first_name,
:last_name => last_name
)
Reservation.stub(:credit_card).and_return(mock_cc)
This would make it so when your model called credit_card it would return a mocked object.

Rails 3.0 Skip validations for nested attributes

I need to be able to save a record without running validations on itself or its nested attributes. I'm stuck in Rails 3.0, and I cannot update to a newer version.
I have a report, each report has many responses (answers to questions). The responses are nested in the report form.
There are two ways the user should be able to save the report: Submit for review, where all validations are run, and Save And Finish Later, where no validations are run for the report or the nested responses. This needs to work for both create and update actions.
I am currently trying to use conditional validations. This works for update but not create. The problem is this line:
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
The report doesn't exist yet, so active record can't find this responses's report. That's where it crashes.
There are a lot some suggested solutions for this problem, but I couldn't get them working in Rails 3.0. update_attributes(attributes, :validate => false), for instance, is not available in Rails 3.0.
So, how do I skip the validations in the nested attributes?
class Report < ActiveRecord::Base
has_many :responses, :order => "created_at asc", :autosave => true
accepts_nested_attributes_for :responses
...
end
class Response < ActiveRecord::Base
belongs_to :report
validates_associated :report
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
validate :correct_answer_or_comment, :if => Proc.new { |response| !response.report.finish_later? }
end
class ReportsController < BaseController
def update
#report = Report.find(params[:id])
#report.attributes = params[:report]
if params[:finish_later].nil?
#report.update_attribute(:finish_later, false)
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save(:validate => false)
redirect_to :action => :index
end
end
def create
#report = Report.new(params[:report])
if params[:finish_later].nil?
#report.finish_later = false
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save!(:validate => false)
redirect_to :action => :index
end
end
end
Not sure if it will work with nested attributes, though I think it should... but give ValidationSkipper a try:
https://github.com/npearson72/validation_skipper
Just make sure you call skip_validation_for on the object you want to skip. Since nested attributes pass behavior to their children, you might be able to call this method directly on the parent object. Give it a try.

Multiple (n) identical nested forms generated square-times(n*n) when validation fails

User has two addresses shipping(:address_type=0) and billing(:address_type=1)
User form with 2 classic nested forms for each address type are generated square times every submit and failed validation.
Models:
class User < ActiveRecord::Base
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses
validates_associated :addresses
end
class Address < ActiveRecord::Base
belongs_to :user
validates :user, :address_type, :first_name, :last_name, :street
end
Controller
class UsersController < ApplicationController
public
def new
#user = User.new
#shipping_address = #user.addresses.build({:address_type => 0})
#billing_address = #user.addresses.build({:address_type => 1})
end
def create
#user = User.new(params[:user])
if #user.save
#fine
else
render => :new
end
end
Uncomplete Form
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses, #shipping_address do |f|
=f.hidden_field :address_type, :value => 0
=ff.fields_for :addresses, #billing_address do |f|
=f.hidden_field :address_type, :value => 1
=ff.submit
The form should look like this:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses do |f|
Nothing else.
Addressess is already a collection, so you should have just one rendering of it.
Also that ":addresses, #shipping_address" makes it to render addresses AND shipping address, even if it's included in #user.addresses.
The addressess built in new action will show there because they are in the addresses collection.
EDIT:
If you need only these two addresses, you can sort it and pass it to fields_for directly:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for ff.object.addresses.sort{|a,b| a.address_type <=> b.address_type } do |f|
That should do it.
Surprised? I guess not but I was. I found it am I correct? And its stupid and simple.
There is no #shipping_address nor #billing_address when validation fails and rendering the new action (the form) again. But #user has already 2 addresses builded and nested form behave correctly to render each twice for first time failed validation.
def create
#user = User.new(params[:user])
if #user.save
#fine
else
#user.addresses.clear
#user_address = #user.addresses.build({:address_type => 0})
#user_address.attributes = params[:user][:addresses_attributes]["0"]
#billing_address = #user.addresses.build({:address_type => 1})
#billing_address.attributes = params[:user][:addresses_attributes]["1"]
render => :new
end
end

A way to refer to this current 'votable' element in an action in Rails (e.g. either #post and #comment)?

I have two models, Post and Comment that have a polymorphic association with another model called Vote.
post.rb and comment.rb have has_many :votes, :as => :votable, :dependent => :destroy
vote.rb has belongs_to :votable, :polymorphic => true
This controller has two actions one to add up votes for Post and the other for Comment:
controllers/votes_controller.rb:
class VotesController < ApplicationController
def vote_up
#post = Post.find(params[:id])
if #post.votes.exists?(:user_id => current_user.id)
#notice = 'You already voted'
else
#vote = #post.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
def vote_up2
#comment = Comment.find(params[:id])
if #comment.votes.exists?(:user_id => current_user.id)
#notice2 = 'You already voted'
else
#vote2 = #comment.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
end
I think that's unnecesary. Is there any way of using a single name to refer to the current votable element or either #post and #comment?
Edit
routes.rb:
get 'votes/:id/vote_up' => 'votes#vote_up', as: 'vote_up'
get 'votes/:id/vote_down' => 'votes#vote_down', as: 'vote_down'
The vote_up action should be implemented in your posts and comments controller respectively. Users are voting on posts or comments, they're not voting on a vote.
I would extract the voting logic and place it in a module that your models will include, then call it on a votable object from the controller.
in your lib directory, create votable.rb
module Votable
def up_vote_from(usr)
place_vote(1, usr.id)
end
def down_vote_from(usr)
place_vote(-1, usr.id)
end
private
def place_vote(direction, usr_id)
v = self.votes.find_or_create_by_user_id(usr_id)
v.update_attribute(:polarity, direction)
end
end
(This revised code will alter a user's original vote if they vote again. Vote methods will return true if the vote saves, false otherwise.)
In each votable model, such as post.rb and comment.rb, add this line to mix in your voting methods:
include Votable
Now, this can be done in a controller:
#post.up_vote_from current_user # => true
As far as implementation is concerned, you will end up with some repetition in your controllers/routes.
In each votable controller, set something up like:
def cast_vote
#post = Post.find params[:id]
if #post.call("#{params[:updown]}_vote_from", current_user)
respond_to do |format|
format.js
end
else
head :not_found
end
end
(this expects .../posts/123/vote/up for an upvote, .../posts/123/vote/down for a downvote.)
then append each resource to include your vote method:
resources :posts do
member do
post 'vote/:updown', :to => "posts#cast_vote", :as => :vote_on
end
end
which can be called in your views with:
<%= button_to "Up", :url => vote_on_post_path(#post, "up"), :remote => true %>
<%= button_to "Down", :url => vote_on_post_path(#post, "down"), :remote => true %>
This is a lot less work than it looks. It'll make sense once you put it in place. It'll make even more sense if you code it in by hand vs. cut and paste. :)