How to get multiple values of a record with map - ruby-on-rails

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

Related

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.

Ruby add variables to an existing object?

How can I add variables to an existing obejct?
I have a list of chat rooms and I want to add a new variable for each chat to use at my view:
Example I want to add total users of chat
def index
chats_all = ChatRoom.all
#chats = Array.new
chats_all.each |chat|
chat.total_users = 10
#chats << chat
end
#chats
end
total_users is not an attribute of ChatRoom class.
[EDIT - explaim better after #jvillian great awnser]
I don't want total_users as an attribute of User class.
I just want to add as a variable to use at this one single page. For json rails already let my add new attributes to objects. Just need to use as_json().map and a merge()
Example:
def index
chats = chats.as_json().map {
|chat|
chat.merge(
total_users: 10
}
response = { chats: chats }
render json: response
end
Now I got an json with chats and each chat has total_users attribute.
I want to know if I can do something like this with objects, just add a temporary variable to use at index page.
Try
class ChatRoom < ActiveRecord::Base
attr_accessor :total_users
end
You can read more in the docs.
Then, index could look like:
def index
#chats = ChatRoom.all.map do |chat|
chat.total_users = 10
chat
end
end
Alternatively, I would be tempted to do something like:
class ChatRoom < ActiveRecord::Base
TOTAL_USERS = 10
attr_accessor :total_users
def total_users
#total_users || TOTAL_USERS
end
end
And then:
def index
#chats = ChatRoom.all
end
Now, you'll get
#chats.first.total_users
=> 10
You can set total_users to something else if you like, but it will default to 10.
Here's a potential approach using OpenStruct:
def index
#chats = ChatRoom.all.map do |chat|
OpenStruct.new(
chat.
attributes.
merge!(total_users: 10)
)
end
end
Now, you can do:
#chats.each do |chat|
puts chat.total_users
end
which will return 10.
BTW and TBH, I do something like that last sort of thing (using OpenStruct or custom decorators) all the time. In my more recent apps, views never have direct access to models.
Maybe you want to send the response to the view as an array and scan to show informations?
def index
#chats = ChatRoom.all.as_json().map { |chat| chat.merge("total_users" => 10) }
end
Then access #chats, which is actually an array of hashes, view:
<% #chats.each do |chat| %>
<p><%= chat["total_users"] %></p>
<% end %>
You can check how #chats is structured by <p><%= #chats %></p>
I maybe made some syntax error.
To create temporary custom Objects without add new attributes to database Struct solve my problem.
I can create a Struct with chat room info and total users
chat_info = Struct.new(:name, :total_users, :messages)
chat_temp = []
chats = ChatRoom.where(condition)
chats.each do |chat|
chat_temp << chat_info.new("nome", 100, messages)
end

How to go through 2 models in a loop in ruby on rails ?

I am using MailKick gem and I am sending emails to users from id 1 to 1000 who have not opted out to get emails.
So I have
#users = User.where('id >= 1').where('id <= 1000')
#opt_out_users = Mailkick.opt_outs.where(active: true)
User model has id, email and name as fields.
MailKick model has id, email, user_id and active as fields.
Now I can run the code
#users.each do |user|
//Code to send emails to each user
end
but I am not able to figure out how to filter out those users who have opted out.
As documentation says, you should add mailkick_user to your model:
class User < ActiveRecord::Base
mailkick_user
end
Then scope .not_opted_out will be avaliable for you:
User.not_opted_out.where('id <= 1000').each { |user| user.do_smth }
You should be able to use
#users = User.where.not(
id: Mailkick.opt_outs.where(active: true).pluck(:user_id)
).where("id <= 1000")
The clause .where('id >= 1') is redundant.
You can do this in one command:
#users_who_have_not_opted_out = User.where(id: 1..1000)
.where.not( id: MailKick.op_outs.where(active: true).pluck(:user_id) )
The first where function gets all ids between 1 and 1000. The second where.not statement returns all ids that are not in the opt_out list. The pluck(:user_id) turns the MailKick.opt_outs.where(active:true) association into an array of user_ids
You can then run:
#users_who_have_not_opted_out do |user|
# Here you would execute your send email command on user
end
you can get all user ids who are not opted out like this,
#opt_out_users = Mailkick.opt_outs.where(active: true,:user_id=>{'$gte'=>1,'$lte'=>1000}).map(&:user_id);
The above statement will return the user ids in an array.
Next you can can search user object using those ids.
#user = User.where(:id=>{'$in'=> #opt_out_users});
And loop through each user
#user.each do |user|
#code for sending mails
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: loops and plucking items out best practices

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

Resources