Rails 3: loops and plucking items out best practices - ruby-on-rails

I am working on a small app that allows for users to add a product (or subscription) to their cart. Upon creating their account, the new user is sent to a "bundle" page where it asks if they would like to add a different subscription to a different product altogether for a bundled price.
Here is where I am stuck: Upon submitting the user's credit card info I get slightly "lost in translation" when trying to setup the bundle pricing to submit to Authorize.net (I understand how to authnet, not the question here).
Here is what I have so far:
current_order.products.includes(:client).each do |product|
transaction = current_order.submit_order_to_authnet(product)
if transaction.result_code == 'Ok'
new_group = Group.create!(:name => "#{current_user.full_name} #{product.title}", :type => 'school', :start_date => Time.now, :status => 'active', :site_id => 1)
primary = session[:primary_product_id].eql?(product.id) ? true : false
# Add subscription to Group
new_group.add_subscription(product, current_order, transaction.subscription_id, 'active', primary)
# Add Subscription to CurrentOrder
current_order.subscriptions << new_group.subscriptions.last
# Add user to NewGroup
current_user.groups << new_group
# Create New Group Admin
new_group.group_admins.create(:user_id => current_user.id)
# Send success email
OrderMailer.checkout_confirmation(current_user).deliver
else
errors << transaction.result_code
end
end
I am trying to figure out the best solution when it comes to looping through each product in the users current_order because the second subscription in the users cart is the subscription that gets the discount applied too. I know I can write something like this:
current_order.products.includes(:client).each do |product|
if current_order.products.many? and product == current_order.products.last
# run discount logic
else
# continue with authnet for single subscription
end
end
But I am just not sure if that is a best practice or not. Thoughts?

So the only subscription that doesn't get discounted is the first one? Why not write it like this:
current_order.products.includes(:client).each do |product|
if product == current_order.products.first
# continue with authnet for single subscription
else
# run discount logic
end
end

Related

How do I enter a record for every user where condition is true in Rails

So I have app that has political candidates.
When a new political candidate is entered, I want to enter a notification into the notifications table for every user that's state is equal to the state of the new candidate being entered.
Ultimately, I want to enter in records to the notification table for every single user where that condition is met.
I know I'm way off, but here's where I'm at now. I'm trying to loop through each user and then enter this record when that condition is true.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
User.each do |u|
if Candidate.state == User.state
#notification = Notification.new(:message => 'Message', :user_id => U.id)
#notification.save
else
end
end
else
render('new')
end
end
The candidate is created with this code, but the notifications aren't working. Basically I have two users where their state equals "Arizona" and I would expect if I create a new candidate where the state is "Arizona" that I should get two record into notifications, one with each user ID.
I think you got a bit mixed up between classes and instances. Here's the relevant bit:
#candidate = Candidate.new(candidate_params)
...
User.each do |u|
if Candidate.state == User.state
...
end
end
In your code Candidate is a class, and #candidate holds the recently created instance of a Candidate. Likewise, User is a class and u holds a User instance (on each loop iteration). Your comparison should actually use the instances rather than the classes:
if #candidate.state == u.state
Having sorted that, it's worth noting that your code has a couple of other errors -- User.each won't work. You need to specify a selector to get a list of User objects to loop through. One way would be to call User.all.each (which looking at your code is probably what you were trying). That pulls all User objects. But, since users can be from anywhere, if you do that you will cycle through a lot of users you don't need to.
Since all you need is users whose state matches the new candidate, you can use the where() method to pre-filter the list you are looping through. That way you don't need the if at all.
#candidate = Candidate.new(candidate_params)
...
User.where(state: #candidate.state).each do |u|
#notification = Notification.new(message: 'Message', user: u)
#notification.save
end
The other problem in your code is in the line to create a notification. You use U.id but the loop variable is lower case u. As an added tip, you don't need to set the object ID specifically. If you just pass the User object (as in the code above), Rails is smart enough to figure out the rest.
For performance don't iterate all users, you can search users that match the candidate's state then create notification for each user.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
users = User.where(state: #condidate.state)
users.each { |user| #notification = Notification.create(:message => 'Message', :user_id =user.id } if users
else
render 'new'
end
end

How to refactor this code to make it more readable and efficient?

I need help refactoring the code.I ve tried by best landed with the following code. Is there anything That I can do
class OrdersController < ApplicationController
before_action :get_cart
before_action :set_credit_details, only: [:create]
# process order
def create
#order = Order.new(order_params)
# Add items from cart to order's ordered_items association
#cart.ordered_items.each do |item|
#order.ordered_items << item
end
# Add shipping and tax to order total
#order.total = case params[:order][:shipping_method]
when 'ground'
(#order.taxed_total).round(2)
when 'two-day'
#order.taxed_total + (15.75).round(2)
when "overnight"
#order.taxed_total + (25).round(2)
end
# Process credit card
# Check if card is valid
if #credit_card.valid?
billing_address = {
name: "#{params[:billing_first_name]} # .
{params[:billing_last_name]}",
address1: params[:billing_address_line_1],
city: params[:billing_city], state: params[:billing_state],
country: 'US',zip: params[:billing_zip],
phone: params[:billing_phone]
}
options = { address: {}, billing_address: billing_address }
# Make the purchase through ActiveMerchant
charge_amount = (#order.total.to_f * 100).to_i
response = ActiveMerchant::Billing::AuthorizeNetGateway.new(
login: ENV["AUTHORIZE_LOGIN"],
password: ENV["AUTHORIZE_PASSWORD"]
).purchase(charge_amount, #credit_card, options)
unless response.success?
#order.errors.add(:error, "We couldn't process your credit
card")
end
else
#order.errors.add(:error, "Your credit card seems to be invalid")
flash[:error] = "There was a problem processing your order. Please try again."
render :new && return
end
#order.order_status = 'processed'
if #order.save
# get rid of cart
Cart.destroy(session[:cart_id])
# send order confirmation email
OrderMailer.order_confirmation(order_params[:billing_email], session[:order_id]).deliver
flash[:success] = "You successfully ordered!"
redirect_to confirmation_orders_path
else
flash[:error] = "There was a problem processing your order. Please try again."
render :new
end
end
private
def order_params
params.require(:order).permit!
end
def get_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
end
def set_credit_details
# Get credit card object from ActiveMerchant
#credit_card = ActiveMerchant::Billing::CreditCard.new(
number: params[:card_info][:card_number],
month: params[:card_info][:card_expiration_month],
year: params[:card_info][:card_expiration_year],
verification_value: params[:card_info][:cvv],
first_name: params[:card_info][:card_first_name],
last_name: params[:card_info][:card_last_name],
type: get_card_type # Get the card type
)
end
def get_card_type
length, number = params[:card_info][:card_number].size, params[:card_info][:card_number]
case
when length == 15 && number =~ /^(34|37)/
"AMEX"
when length == 16 && number =~ /^6011/
"Discover"
when length == 16 && number =~ /^5[1-5]/
"MasterCard"
when (length == 13 || length == 16) && number =~ /^4/
"Visa"
else
"Unknown"
end
end
end
Products with a price attribute. We have shopping Carts that have many Products through the OrderedItems join table. An OrderedItem belongs_to a Cart and a Product. It has a quantity attribute to keep track of the number of products ordered.
The OrderedItem also belongs_to an Order
I wanted to know if it can be refactored further.
First of all you should move all that business logic from the controller into models and services (OrderProcessService, PaymentService). All the controller's private methods belong to a PaymentService.
Split the code into smaller methods.
If doing that on the model level some things that come into my mind when reading your code are the following:
#order.add_items_from_cart(#cart)
#order.add_shipping_and_tax(shipping_method)
Orders should be first saved (persisted in DB), then processed (purchased with changing their status).
#order.save might fail after a successful payment, so a client will lose the money and not get their order.
the purchasing is an important and critical process, so you should make sure everything is ready for it (the order is valid and saved)
a client should be able to purchase later or after the payment page is accidentally reloaded without filling the form again
normally when a payment is performed you should send an order ID to the payment system. The payment system will store the ID and you will always know which order the payment belongs to.
There are a lot of other things to consider. You have a lot of work to do.

How to get multiple values of a record with map

In my application I can have multiple accounts and accounts can have multiple emails. I have a method that counts all the unique email from every account, but that is not what I want however.
Instead I want to return all the unique email from just one account NOT all, as the method is currently doing.
Here is my current method:
class AccountEmails
def self.count
accounts = Account.all
alert = accounts.map do |a|
a.users.first.alert_email.split(",")
end
billing = accounts.map do |a|
a.users.first.billing_email.split(",")
end
user = accounts.map do |a|
a.users.first.email.split(",")
end
snitch = accounts.map do |a|
a.snitches.map { |s| s.alert_email.split(",") }
end
[alert, billing, user, snitch].flatten.uniq.count
end
end
This will return all the email that are unique from all the accounts. I want to return all the unique email for each account, so account 1 could have four unique email and account 2 could have five unique email.
It sounds like you're saying you want a single method that gives you all the unique emails for each account. If I'm understanding you, I would do something like this:
class Account
def all_emails
# I'm assuming here that you actually only want the unique
# emails on the first user for each account
user = self.users.first
[
user.alert_email.split(","),
user.billing_email.split(","),
user.email.split(","),
self.snitches.map{|snitch| snitch.alert_email.split(",") }
].flatten
end
def unique_emails
all_emails.uniq
end
end
class AccountEmails
def self.unique
Account.all.includes(:snitches).map do |account|
account.uniq_emails
end
end
def self.count
uniq.flatten.count
end
end

Overriding collection<< method

I have two models Board and Feed in many-to-many relationship with join :through model Subscription.
Lately I added root_id field to Subscription, and I'd like to setup this field when I do #board.feeds << #feed So it would be like #board.feeds << #feed, root_id: 10
According to rails docs I can override these methods, but not sure how.
def feeds << (??? - how should I setup arguments here? )
super
#super creates new Subscription rekord, but how can I access it
#to set root_id field?
#For now let say I accessed it as subscription
if root_id
subscription.root_id = root_id
else
subscription.root_id = self.id
end
subscription.save
#return something regular collection << returns
end
Does the root_id on Subscription represent any aspect of the Board or Feed? You could maybe do that work in a callback on Subscription instead:
# Subscription
before_save :assign_root_id
def assign_root_id
self.root_id = feed.user.id # Whatever root is ....
end

Rails 3 question - locking row for specific time

I'm building a rails 3 app in which it sells a limited number of items. I'm looking for a way to hold an item for a specific amount of time so that when someone selects an item, they have time to purchase it before someone else can purchase it before them. I have done some research as to row locking but haven't found a usable method thus far for specifying a time.
Thanks for any help or ideas
This is a typical workflow pattern, where you acquire an object for a duration. You can achieve this easily by implementing application level locks.
1) Add lock fields to the model.
locker_id
lock_until
2) Now you can implement this logic in the Product model.
class Product
belongs_to :locker, :class_name => "User",
:condition => lambda { {:conditions => ["lock_until < ? ", Time.now]}}
def locked?
!lock_until.nil? and lock_until > Time.now
end
def lock_for_duration(usr, duration=10.minutes)
return false if locked?
self.locker_id = user.id
self.lock_until = duration.from_now
self.save
end
def release_lock
return true unless locked?
self.locker_id = nil
self.lock_until = nil
self.save
end
end
Here is how to use this:
usr = User.first
product.lock_for_duration(usr, 30.minutes)
product.locked?
product.locker?
I would recommend setting a locked_until timestamp that's checked whenever someone attempts to buy one of these items. If there are no items with a locked_until time in the past, then all items are "sold out". For actual selling of the items, I would have a sold boolean field.

Resources