Ruby: Playing MP3s based on if statement - ruby

I have a ruby application/website where users can scan qr codes and make donations to bitcoin wallets. Whenever a donations occurs a transaction and event is created and the bitcoins wallet value is updated. Here is the code below
require 'date'
module Server
class Event
include DataMapper::Resource
property :id, Serial
property :duration, Float, required: true, default: ->(x, y) { 1.0 }
property :created_at, DateTime, required: true, index: true, default: ->(x, y) { DateTime.now }
property :valve, Integer, required: true
belongs_to :transaction, index: true
belongs_to :bucket, index: true
def self.register(wallet, amount, hash)
bucket = Bucket.first(wallet: wallet)
unless bucket
raise DataMapper::ObjectNotFoundError, "Bucket not found for wallet #{wallet}"
end
tr = Transaction.create!({
wallet: wallet,
amount: amount,
thash: hash,
created_at: DateTime.now
})
ev = Event.create!({
valve: bucket.valve,
duration: calculate_duration(amount * bucket.multiplier),
transaction: tr,
bucket: bucket,
created_at: tr.created_at
})
bucket.update(amount: bucket.amount + amount)
end
def amount
self.transaction { |x| x.amount }
end
def self.calculate_duration(amount)
[[amount, Config.server.min_duration].max, Config.server.max_duration].min
end
def simple
return {
valve: valve,
duration: duration_millis
}
end
def duration_millis
(duration * 1000).to_i
end
def to_hash(is_simple)
if is_simple
simple
else
attributes.merge simple
end
end
def self.latest(valve = nil)
if valve.nil?
Event.first(order: [:created_at.asc])
else
Event.first(order: [:created_at.asc], valve: valve)
end
end
def self.latest!(valve = nil)
result = self.latest(valve)
result.destroy unless result.nil?
result
end
end
end
There are 3 wallets total. so 0, 1 & 2. My question is how can I play a specific sound depending on what wallet is updated using an if statement after the bucket update function is called.

Related

Ruby method refactor multi line assignment to pass RuboCop

I have been trying to tune this method that sets up complex assignment and I am looking for other options to make this function pass the cops.
Would anyone have thoughts to point me in the right direction?
Right now, I am tinkering with breaking out the two inner .map calls.
Failing Cops
Assignment Branch Condition size for parse_items is too high. [24.08/15]
def parse_items
Avoid multi-line chains of blocks.
end.compact.map do |opt|
The problem code
def parse_items
options = parse_relationships
options = options.select { |opt| opt['type'] == 'product_options' }
options.map do |opt|
parse_included.detect { |x| x['id'] == opt['id'] }
end.compact.map do |opt|
{
group_id: #payload['id'],
originator_id: opt['id'],
price: opt['attributes']['price'],
description: opt['attributes']['name'],
exp_quantity: opt['attributes']['quantity'].to_i,
title: parse_attributes['name'],
image_originator_url: 'image_for_product',
updated_at: timestamp
}
end
end
Helper Methods
private
def parse_data
#payload['data']
rescue
[]
end
def parse_included
#payload['included']
rescue
[]
end
def parse_attributes
#payload['data']['attributes']
rescue
[]
end
def parse_relationships
#payload['data']['relationships']['options']['data']
rescue
[]
end
def timestamp
Time.parse(parse_attributes['updated_at'])
end
Updated Errors
In the spec: wrong number of arguments (given 2, expected 1) for Failure/Error: SELECT = ->(opt) { opt['type'] == 'product_options' }
Assignment Branch Condition size for parse_items is too high. [17/15]
Updated Code
SELECT = ->(opt) { opt['type'] == 'product_options' }
MAP = ->(opt) { parse_included.detect { |x| x['id'] == opt['id'] } }
def parse_items
parse_relationships.select(&SELECT).map(&MAP).compact.map do |opt|
{
group_id: #payload['id'],
originator_id: opt['id'],
price: opt['attributes']['price'],
description: opt['attributes']['name'],
exp_quantity: opt['attributes']['quantity'].to_i,
title: parse_attributes['name'],
image_originator_url: 'image_for_product',
updated_at: timestamp
}
end
end
I was able to refactor this making it far cleaner and pass all the cops! Hooray!
def parse_items
assign_item_attributes(select_included_option(select_item_options(parse_relationships['options']['data'])))
end
def select_included_option(options)
options.map do |opt|
parse_included.detect { |x| x['id'] == opt['id'] }
end
end
def assign_item_attributes(options)
options.compact.map do |opt|
{
group_id: #payload['id'],
originator_id: opt['id'],
price: opt['attributes']['price'],
description: opt['attributes']['name'],
exp_quantity: opt['attributes']['quantity'].to_i,
title: parse_attributes['name'],
image_originator_url: parse_image,
updated_at: parse_timestamp
}
end
end
def select_item_options(options)
options.select { |opt| opt['type'] == 'product_options' }
end

Ruby aggregation with objects

Lets say I have something like this:
class FruitCount
attr_accessor :name, :count
def initialize(name, count)
#name = name
#count = count
end
end
obj1 = FruitCount.new('Apple', 32)
obj2 = FruitCount.new('Orange', 5)
obj3 = FruitCount.new('Orange', 3)
obj4 = FruitCount.new('Kiwi', 15)
obj5 = FruitCount.new('Kiwi', 1)
fruit_counts = [obj1, obj2, obj3, obj4, obj5]
Now what I need, is a function build_fruit_summary which due to a given fruit_counts array, it returns the following summary:
fruits_summary = {
fruits: [
{
name: 'Apple',
count: 32
},
{
name: 'Orange',
count: 8
},
{
name: 'Kiwi',
count: 16
}
],
total: {
name: 'AllFruits',
count: 56
}
}
I just cannot figure out the best way to do the aggregations.
Edit:
In my example I have more than one count.
class FruitCount
attr_accessor :name, :count1, :count2
def initialize(name, count1, count2)
#name = name
#count1 = count1
#count2 = count2
end
end
Ruby's Enumerable is your friend, particularly each_with_object which is a form of reduce.
You first need the fruits value:
fruits = fruit_counts.each_with_object([]) do |fruit, list|
aggregate = list.detect { |f| f[:name] == fruit.name }
if aggregate.nil?
aggregate = { name: fruit.name, count: 0 }
list << aggregate
end
aggregate[:count] += fruit.count
aggregate[:count2] += fruit.count2
end
UPDATE: added multiple counts within the single fruity loop.
The above will serialize each fruit object - maintaining a count for each fruit - into a hash and aggregate them into an empty list array, and assign the aggregate array to the fruits variable.
Now, get the total value:
total = { name: 'AllFruits', count: fruit_counts.map { |f| f.count + f.count2 }.reduce(:+) }
UPDATE: total taking into account multiple count attributes within a single loop.
The above maps the fruit_counts array, plucking each object's count attribute, resulting in an array of integers. Then, reduce is getting the sum of the array's integers.
Now put it all together into the summary:
fruits_summary = { fruits: fruits, total: total }
You can formalize this in an OOP style by introducing a FruitCollection object that uses the Enumerable module:
class FruitCollection
include Enumerable
def initialize(fruits)
#fruits = fruits
end
def summary
{ fruits: fruit_counts, total: total }
end
def each(&block)
#fruits.each &block
end
def fruit_counts
each_with_object([]) do |fruit, list|
aggregate = list.detect { |f| f[:name] == fruit.name }
if aggregate.nil?
aggregate = { name: fruit.name, count: 0 }
list << aggregate
end
aggregate[:count] += fruit.count
aggregate[:count2] += fruit.count2
end
end
def total
{ name: 'AllFruits', count: map { |f| f.count + f.count2 }.reduce(:+) }
end
end
Now pass your fruit_count array into that object:
fruit_collection = FruitCollection.new fruit_counts
fruits_summary = fruit_collection.summary
The reason the above works is by overriding the each method which Enumerable uses under the hood for every enumerable method. This means we can call each_with_object, reduce, and map (among others listed in the enumerable docs above) and it will iterate over the fruits since we told it to in the above each method.
Here's an article on Enumerable.
UPDATE: your multiple counts can be easily added by adding a total attribute to your fruit object:
class FruitCount
attr_accessor :name, :count1, :count2
def initialize(name, count1, count2)
#name = name
#count1 = count1
#count2 = count2
end
def total
#count1 + #count2
end
end
Then just use fruit.total whenever you need to aggregate the totals:
fruit_counts.map(&:total).reduce(:+)
fruits_summary = {
fruits: fruit_counts
.group_by { |f| f.name }
.map do |fruit_name, objects|
{
name: fruit_name,
count: objects.map(&:count).reduce(:+)
}
end,
total: {
name: 'AllFruits',
count: fruit_counts.map(&:count).reduce(:+)
}
}
Not very efficient way, though :)
UPD: fixed keys in fruits collection
Or slightly better version:
fruits_summary = {
fuits: fruit_counts
.reduce({}) { |acc, fruit| acc[fruit.name] = acc.fetch(fruit.name, 0) + fruit.count; acc }
.map { |name, count| {name: name, count: count} },
total: {
name: 'AllFruits',
count: fruit_counts.map(&:count).reduce(:+)
}
}
counts = fruit_counts.each_with_object(Hash.new(0)) {|obj, h| h[obj.name] += obj.count}
#=> {"Apple"=>32, "Orange"=>8, "Kiwi"=>16}
fruits_summary =
{ fruits: counts.map { |name, count| { name: name, count: count } },
total: { name: 'AllFruits', count: counts.values.reduce(:+) }
}
#=> {:fruits=>[
# {:name=>"Apple", :count=>32},
# {:name=>"Orange", :count=> 8},
# {:name=>"Kiwi", :count=>16}],
# :total=>
# {:name=>"AllFruits", :count=>56}
# }

How to limit test data creation when running two tests with FactoryGirl

I have 48 records being created by FactoryGirl, the records use sequence so that they are all unique.
When I run a test, I get all 48 records being created with names as follow Skill_1 to Skill_48.
When I run a subsequent test, I get an additional 48 records created, these have different new values, Skill_49 to Skill_96
I really want my 2nd test to use the same data set as the first test but cannot figure out how to do so.
I have worked out that the data is RE-CREATED every time, but the sequence does not reset and so the names are different on every run
I've included my code here
# Factory
FactoryGirl.define do
factory :skill do
provisioned true
trait :skill do
skill true
end
trait :language do
language true
end
trait :qualification do
qualification true
end
trait :role do
role true
end
trait :personal_attribute do
personal_attribute true
end
sequence :name do |n|n
type = '(skill)'
if language
type = '(language)'
end
if qualification
type = '(qualification)'
end
if role
type = '(role)'
end
if personal_attribute
type = '(personal_attribute)'
end
"skill#{n} #{type}"
end
end
end
UNIT TEST is HERE
describe SkillQueryService do
let(:skills) { create_list(:skill, 10, :skill) }
let(:languages) { create_list(:skill, 2, :language) }
let(:qualifications) { create_list(:skill, 3, :qualification) }
let(:roles) { create_list(:skill, 4, :role) }
let(:personal_attributes) { create_list(:skill, 5, :personal_attribute) }
let(:unprovisioned_skills) { create_list(:skill, 10, :skill, :provisioned => false) }
let(:unprovisioned_languages) { create_list(:skill, 2, :language, :provisioned => false) }
let(:unprovisioned_qualifications) { create_list(:skill, 3, :qualification, :provisioned => false) }
let(:unprovisioned_roles) { create_list(:skill, 4, :role, :provisioned => false) }
let(:unprovisioned_personal_attributes) { create_list(:skill, 5, :personal_attribute, :provisioned => false) }
context 'sugguest' do
it 'returns 20 suggested provisioned skills' do
# Build TEST data
service = SkillQueryService.new
rows = service.suggest('skill')
# rows.each do |r|
# display_skill(r)
# end
# THIS CODE PRINTS OUT SKILLS 1-48
expect(rows.length).to eq(20)
end
it 'returns 20 suggested (UN)-provisioned skills' do
# Build TEST data
full_data_set
service = SkillQueryService.new
rows = service.suggest('skill')
# rows.each do |r|
# display_skill(r)
# end
# THIS CODE PRINTS OUT SKILLS 49-96
# HOW do I get it to have the same data as above, SKILLS 41-48
expect(rows.length).to eq(20)
end
end
def full_data_set
skills
languages
qualifications
roles
personal_attributes
unprovisioned_skills
unprovisioned_languages
unprovisioned_qualifications
unprovisioned_roles
unprovisioned_personal_attributes
end
def display_skill(skill)
PL.kv 'name', skill.name
PL.kv 'provisioned', skill.provisioned
PL.kv 'skill', skill.skill
PL.kv 'language', skill.language
PL.kv 'qualification', skill.qualification
PL.kv 'role', skill.role
PL.kv 'personal_attribute', skill.personal_attribute
PL.line
end
def display_skills
PL.line
Skill.all.each do |r|
display_skill(r)
end
end
end
I found my answer here
how-can-i-reset-a-factory-girl-sequence
before(:each) do
FactoryGirl.reload
end

Exporting to CSV and Summing hours in Ruby on Rails

I have a Rails 3.2.21 app where I'm building time clock functionality. I'm currently writing a to_csv method that should do the following:
Create a header row with column names
Iterate through a block of input (records) and display the employee username, clock_in, clock_out, station, and comment objects, then finally on the last line of the block display the total hours.
In between each user I want to display a sum of their total hours. As you can see in the to_csv method I'm able to get this to work "hackish" by shoveling an array of csv << [TimeFormatter.format_time(ce.user.clock_events.sum(&:total_hours))] into the CSV. The end result is it does give me the proper total hours for each employee's clock_events, but it repeats it after every entry because I'm obviously iterating over a block.
I'd like to figure out a way to abstract this outside of the block and figure out how to shovel in another array that calculates total_hours for all clock events by user without duplicate entries.
Below is my model, so if something is not clear, please let me know. Also if my question is confusing or doesn't make sense let me know and I'll be happy to clarify.
class ClockEvent < ActiveRecord::Base
attr_accessible :clock_in, :clock_out, :user_id, :station_id, :comment
belongs_to :user
belongs_to :station
scope :incomplete, -> { where(clock_out: nil) }
scope :complete, -> { where("clock_out IS NOT NULL") }
scope :current_week, -> {where("clock_in BETWEEN ? AND ?", Time.zone.now.beginning_of_week - 1.day, Time.zone.now.end_of_week - 1.day)}
scope :search_between, lambda { |start_date, end_date| where("clock_in BETWEEN ? AND ?", start_date.beginning_of_day, end_date.end_of_day)}
scope :search_by_start_date, lambda { |start_date| where('clock_in BETWEEN ? AND ?', start_date.beginning_of_day, start_date.end_of_day) }
scope :search_by_end_date, lambda { |end_date| where('clock_in BETWEEN ? AND ?', end_date.beginning_of_day, end_date.end_of_day) }
def punch_in(station_id)
self.clock_in = Time.zone.now
self.station_id = station_id
end
def punch_out
self.clock_out = Time.zone.now
end
def completed?
clock_in.present? && clock_out.present?
end
def total_hours
self.clock_out.to_i - self.clock_in.to_i
end
def formatted_clock_in
clock_in.try(:strftime, "%m/%d/%y-%H:%M")
end
def formatted_clock_out
clock_out.try(:strftime, "%m/%d/%y-%H:%M")
end
def self.search(search)
search ||= { type: "all" }
results = scoped
# If searching with BOTH a start and end date
if search[:start_date].present? && search[:end_date].present?
results = results.search_between(Date.parse(search[:start_date]), Date.parse(search[:end_date]))
# If search with any other date parameters (including none)
else
results = results.search_by_start_date(Date.parse(search[:start_date])) if search[:start_date].present?
results = results.search_by_end_date(Date.parse(search[:end_date])) if search[:end_date].present?
end
results
end
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours"]
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
csv << [TimeFormatter.format_time(ce.user.clock_events.sum(&:total_hours))]
end
csv << [TimeFormatter.format_time(records.sum(&:total_hours))]
end
end
end
With some help from a friend and researching APIdocs I was able to refactor the method as so:
def self.to_csv(records = [], options = {})
CSV.generate(options) do |csv|
csv << ["Employee", "Clock-In", "Clock-Out", "Station", "Comment", "Total Shift Hours"]
# records.group_by{ |r| r.user }.each do |user, records|
records.each do |ce|
csv << [ce.user.try(:username), ce.formatted_clock_in, ce.formatted_clock_out, ce.station.try(:station_name), ce.comment, TimeFormatter.format_time(ce.total_hours)]
#csv << [TimeFormatter.format_time(records.select{ |r| r.user == ce.user }.sum(&:total_hours))]
end
records.map(&:user).uniq.each do |user|
csv << ["Total Hours for: #{user.username}"]
csv << [TimeFormatter.format_time(records.select{ |r| r.user == user}.sum(&:total_hours))]
end
csv << ["Total Payroll Hours"]
csv << [TimeFormatter.format_time(records.sum(&:total_hours))]
end
end

SystemStackError - stack level too deep: Using metaprograming with ActiveRecord

I need to receive all abilities for current_user in :json for my backbone app. So the firs idea was to add some think like this:
def receive_user_abilities # we will return onty hash for works and tasks
w = Work.accessible_by(current_ability).map { |w| w = {type: 'Work', id: w.id}) }
t = Task.accessible_by(current_ability).map { |t| t = {type: 'Task', id: t.id}) }
render json: t + w # returs merged hash
end
But both line are particular the same, and I decided to user some metaprograming magic. So my solution was to create new helper, include it to my controller, and pass *arg to newly created module(helper) method. Here it is:
module AbilitiesHelper
def receive_abilities_for *classes
classes.inject([]) { |res, klass| res + eval( klass.to_s.capitalize + '.accessible_by(current_ability).map { |element| element = ({type: ' + klass.to_s.capitalize + ', id: element.id }) }') }
end
end
and here is new call from controller
def receive_user_abilities
render json: receive_abilities_for(:work, :task) # returs merged hash
end
It's basically the same, but for some reason I receive an error SystemStackError - stack level too deep:
Where is the error??
Maybe this approach would be easier?
def receive_abilities_for *classes
classes.inject([]) do |res, klass|
res + klass.accessible_by(current_ability).map do |element|
element = {type: klass.to_s, id: element.id }
end
end
end
And call this method in such way:
def receive_user_abilities
render json: receive_abilities_for(Work, Task)
end
Also, as for me, receive_abilities_for method isn't metaprogramming. Metaprogramming is defining new methods and classes at runtime (I may be mistaken).

Resources