How to DRY up two functions in Ruby? - ruby

Is there a way to DRY up these two Ruby functions by moving the three lines that both functions share to another function?
def format_currency(number)
number_to_currency(number,
:unit => current_user.currency_unit,
:delimiter => current_user.currency_delimiter,
:separator => current_user.currency_separator,
:format => current_user.currency_format
)
end
def format_currency_for_pdf(number, invoice)
number_to_currency(number / invoice.exchange_rate,
:unit => CURRENCIES[invoice.currency]
:delimiter => current_user.currency_delimiter,
:separator => current_user.currency_separator,
:format => current_user.currency_format
)
end
Thanks for any help?

As taro suggests, it will be:
def format_currency(number)
number_to_currency(number,
currency_hash(current_user.currency_unit)
)
end
def format_currency_for_pdf(number, invoice)
number_to_currency(number / invoice.exchange_rate,
currency_hash(CURRENCIES[invoice.currency])
)
end
def currency_hash unit
{
:unit => unit,
:delimiter => current_user.currency_delimiter,
:separator => current_user.currency_separator,
:format => current_user.currency_format
}
end

def format_currency(number)
number_to_currency(number,
currency_hash(current_user.currency_unit)
)
end
def format_currency_for_pdf(number, invoice)
number_to_currency(number / invoice.exchange_rate,
currency_hash(CURRENCIES[invoice.currency])
)
end
def currency_hash(unit)
{
:unit => unit,
:delimiter => current_user.currency_delimiter,
:separator => current_user.currency_separator,
:format => current_user.currency_format
}
end

Related

Automata on ruby

I have the following Automata class:
class DFA
attr_accessor :start, :end, :transition
def initialize(options = {})
#start = options[:start]
#end = options[:end]
#transition = options[:transition]
end
def run(input)
current_state = #start
for i in input
current_state = #transition[current_state][i]
end
return true if #end.include? current_state
return false
end
end
if $PROGRAM_NAME == __FILE__
d = DFA.new(:start => :q1, :end => [:q2],
:transition =>
{ :q1 => {'0' => :q1, '1' => :q2},
:q2 => {'0' => :q3, '1' => :q2},
:q3 => {'0' => :q2, '1' => :q3}
})
puts d.run(['1','1','0','0'])
puts d.run(['1','1','0','0','0'])
puts d.run(['0','0','0'])
end
Output:
True
False
False
The problem is that i want this Automata to be able to 'eat' 'ANY OTHER' symbol. It would be like 'default' or 'else' action. For example:
{ :q1 => {'0' => :q1, 'ANY OTHER SYMBOL' => :q2},
So, it will be possible to write:
puts d.run(['1','1','0','a'])
and get 'True'. What are possible solutions? Thank you!
P.S. I have to write an Automata, which will be able to parse .INI files. May be it would be better not to write a class, but just to form my automata in 'case...when...when...else...' statement? What do you think?
You could define a special symbol:
class DFA
ANY = :any
And calculate the state based on it:
state = #transitions[state][step] || #transitions[state][DFA::ANY]
And use it defining DFA:
{'0' => :q1, '1' => :q2, DFA::ANY => :q2}
Complete example after refactoring:
class DFA
ANY = :any
attr_accessor :start, :end, :transitions
def initialize(options = {})
#start = options[:start]
#end = options[:end]
#transitions = options[:transitions]
end
def run(steps)
state = #start
steps.each do |step|
state = #transitions[state][step] || #transitions[state][DFA::ANY]
raise 'Unexpected Symbol' if state.nil?
end
#end.include? state
end
end
dfa = DFA.new start: :q1,
end: [:q2],
transitions: {
q1: {'0' => :q1, '1' => :q2, DFA::ANY => :q2},
q2: {'0' => :q3, '1' => :q2},
q3: {'0' => :q2, '1' => :q3}
}
puts dfa.run(['Q', '0', '0']) #=> true

comparison of Fixnum with Array failed (ArgumentError)

When the script wants to increment progressbar value it gives me:
C:/Ruby193/lib/ruby/gems/1.9.1/gems/ruby-progressbar-1.4.0/lib/ruby-progressbar/components
/progressable.rb:93:in `<=': comparison of Fixnum with Array failed (ArgumentError)
The Script is shown belove. I ftryed to find solution but no luck. Can Anyone help me with this?
korpusu_id = []
container.divs(:class => "resItem")
.find_all { |div| div.span(:class => "theText", :text => /TestAuto/).exists? }
.each do |korpuss|
id = korpuss.span.parent.parent.attribute_value("id")
id = id[-38..-1]
korpusu_id.push(id)
end
puts ""
puts "Tagad notiek nepieciešamo korpusu dzēšana..."
progress = ProgressBar.create(:title => "Failu dzēšana:", :progress_mark => "|", :format => "%t [%B] %p%%", :total => korpusu_id)
korpusu_id.each do |korp_id|
#b.goto("#{#env}/CorpusMetadataEditor.aspx?id=#{korp_id}")
#b.execute_script("window.confirm = function() {return true}")
delete_poga = #b.link(:id, "ctl00_ContentPlaceHolder1_lnkDeleteCorpora")
if delete_poga.exists?
delete_poga.click
else
puts "Korpuss\n#{#env}/CorpusMetadataEditor.aspx?id=#{korp_id}\nlietotājam #{lietotajs} nav pieejams rediģēšanai.\nTurpinu ar nākošo korpusu!\n---------------------------------------------"
next
end
container.div(:class => "resItem").wait_until_present
progress.increment
end
I found the final solution for my problem. I changed:
progress = ProgressBar.create(:title => "Failu dzēšana:", :progress_mark => "|", :format => "%t [%B] %p%%", :total => korpusu_id.length)
and this worked for me.
Sorry for spam...

Converting OpenStruct/Hash to XML

I have a collection of OpenStruct elements using which I need to build an XML with help of Nokogiri.
collection = [
OpenStruct.new(:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates',
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(
:productType => 'Book',
:productName => 'The Client',
:productAuthorized => 'Y',
:productId => 'BKSUS113246A',
:productVerificationCode => '4546747',
:productPurcTransactionTime => '2012-05-21T13:36:38+05:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'StoreUserS14',
:productAuditTime => '2012-05-21T13:36:38+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '50.14 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
),
:productAuditDetails => OpenStruct.new(
:productAuditNo => '2',
:prodHandledByUser => 'OnlineUserOn008',
:productAuditTime => '2012-05-23T16:16:08+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '84.86 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
)
),
:catalogProductInfo => OpenStruct.new(
:productType => 'Pen',
:productName => 'Reynolds'
:productAuthorized => 'N',
:productId => 'PNSUS228886B',
:productVerificationCode => '2330076',
:productPurcTransactionTime => '2012-04-22T15:06:18+04:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'CCUserA14',
:productAuditTime => '2012-04-26T13:36:38+05:30',
:productAuditAdminId => 'ReyGr1132',
:productPurchaseRate => '20.19 Prcnt',
:productSystemLoggerId => 'WIN-NETW4536'
)
)
)
)]
I tried with below code .. as per your answer (handpick of elements)
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
collection.each do |ctlg|
xml.CatalogStoreNumber ctlg.catalogStoreNumber
xml CatalogStoreId ctlg.catalogStoreId
xml.CatalogOwnerId ctlg.catalogOwnerid
xml.CatalogOwner ctlg.catalogOwner
xml.CatalogList do
prod_count = 0
aud_list_count = 0
collection.each do |prod|
info = prod.catalogList[0].catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId info.productId
xml.ProductVerificationCode info.ProductVerificationCode
xml.ProductPurcTransactionTime info.productPurcTransactionTime
xml.ProductAuditDetails do
collection.each do |aud_dtl|
aud_info = aud_dtl.catalogList[0].catalogProductinfo[0].productAuditDetails
xml.ProductAuditNo aud_info.productAuditNo
xml.ProdHandledByUser aud_info.prodHandledByUser
xml.ProductAuditTime aud_info.productAuditTime
xml.ProductAuditAdminId aud_info.productAuditAdminId
xml.ProductPurchaseRate aud_info.productPurchaseRate
xml.ProductSystemLoggerId aud_info.productSystemLoggerId
# Do whatever you must above to concoct your ProductId
end
aud_list_count = aud_list_count + 1
end
prod_count = prod_count + 1
end
end
end
puts builder.to_xml
I need the Output as below...
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogStoreLocation>UnitedStates</CatalogStoreLocation>
<CatalogOwnerId>TYCT11190</CatalogOwnerId>
<CatalogOwner>McGrawHill Pub.</CatalogOwner>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductAuthorized>Y</ProductAuthorized>
<ProductId>BKSUS113246A</ProductId>
<ProductVerificationCode>4546747</ProductVerificationCode>
<ProductPurcTransactionTime>2012-05-21T13:36:38+05:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
<ProductAuditTime>2012-05-21T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>50.14 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
<ProductAuditTime>2012-05-23T16:16:08+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>84.86 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>UNX-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuthorized>N</ProductAuthorized>
<ProductId>PNSUS228886B</ProductId>
<ProductVerificationCode>2330076</ProductVerificationCode>
<ProductPurcTransactionTime>2012-04-22T15:06:18+04:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
<ProductAuditTime>2012-04-26T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>ReyGr1132</ProductAuditAdminId>
<ProductPurchaseRate>20.19 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>
I tried to loop in the nested Array of OpenStruct of elements, but couldn't land on right logic for that ..
Ref.. How to add child nodes in NodeSet using Nokogiri
Setup Code
require 'ostruct'
require 'nokogiri'
collection = [
OpenStruct.new(
:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates',
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(
:productType => 'Book',
:productName => 'The Client'
)
)
)
]
If You Want To Hand-Pick Your Elements and Data
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod|
info = prod.catalogList.catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId "#{prod.catalogOwnerId}-#{prod.catalogStoreNumber}"
# Do whatever you must above to concoct your ProductId
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductId>TYCT11190-657758</ProductId>
</ProductInfo>
</CatalogList>
</CatalogOrder>
If you want a more generic conversion (an XML representation of your OpenStruct hierarchy) see either of the two solutions below:
One Way to Perform Generic Conversion
# Add all entries of an OpenStruct to an XML builder
# Recursively creates sub-nodes for OpenStruct instances
def ostruct_each(ostruct,xml)
ostruct.instance_variable_get(:#table).each do |field,value|
if value.is_a?(OpenStruct)
xml.send(field) do
ostruct_each(value,xml)
end
else
xml.send(field,value)
end
end
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
ostruct_each(prod_info,xml)
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<catalogStoreNumber>657758</catalogStoreNumber>
<catalogStoreId>CTH6536</catalogStoreId>
<catalogStoreLocation>UnitedStates</catalogStoreLocation>
<catalogOwnerId>TYCT11190</catalogOwnerId>
<catalogOwner>McGrawHill Pub.</catalogOwner>
<catalogList>
<catalogProductInfo>
<productType>Book</productType>
<productName>The Client</productName>
</catalogProductInfo>
</catalogList>
</ProductInfo>
</CatalogList>
</CatalogOrder>
A Different Way to Generically Convert It
# Create a NodeSet of elements for all attributes in an OpenStruct
# Recursively creates child elements for any value that is an OpenStruct
def ostruct_to_elements(xml_doc,ostruct)
Nokogiri::XML::NodeSet.new(
xml_doc,
ostruct.instance_variable_get(:#table).map do |name,val|
xml_doc.create_element(name.to_s).tap do |el|
el << (val.is_a?(OpenStruct) ? ostruct_to_elements(xml_doc,val) : val)
end
end
)
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
xml.parent << ostruct_to_elements(xml.doc,prod_info)
end
end
end
end
end
puts builder.to_xml
Answering your changed question and data.
Here's your source data, summarized:
collection = [
OpenStruct.new(
:foo => 'bar',
:list => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(...)
:catalogProductInfo => OpenStruct.new(...)
)
)
]
The first thing we notice is that collection is an array, but it only has one item. That doesn't seem very useful.
The second—far more important—thing to notice is that you are attempting to use an OpenStruct (the inner one) as an array. This fails quite fully, as the second value completely overwrites the first:
require 'ostruct'
p OpenStruct.new( a:1, a:2 )
#=> #<OpenStruct a=2>
You can't have two values for the same key in a struct. Instead, you probably wanted an array value inside your object. For example:
root = OpenStruct.new(
:foo => 'bar',
:list => [ # this is an array of two distinct objects
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) ),
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) )
]
)
Thirdly, as I noted earlier, I see no good reason for you to be using an OpenStruct here. Instead, just use Hash literals. Combined with the code I provided in my other answer, this is what your data (simplified) and a working solution looks like:
Source Data
MyOrder = {
catalogStoreNumber: '657758', # Ruby 1.9 Hash syntax;
catalogStoreId: 'CTH6536', # same as :catalogStoreId => 'CTH536'
catalogList: {
catalogProductInfo: [
{
productType: 'Book',
productName: 'The Client',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'StoreUserS14' },
{ productAuditNo: '2', prodHandledByUser: 'OnlineUserOn008' }
]
},
{
productType: 'Pen',
productName: 'Reynolds',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'CCUserA14' }
]
}
]
}
}
Generic Conversion
# Adds key/value pairs from a Hash to a Nokogiri::XML::Builder
def hash2xml(hash,xml)
hash.each do |field,value|
name = field.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar"
case value
when Hash then xml.send(name){ hash2xml(value,xml) }
when Array then value.each{ |o| xml.send(name){ hash2xml(o,xml) } }
else xml.send(name,value)
end
end
end
Working Code
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
hash2xml(MyOrder,xml)
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>

I'm using the ruby gem "roo" to read xlsx file, how to return the content of one column as an array?

require 'gchart'
require 'rubygems'
require 'roo'
oo = Excelx.new("datav.xlsx")
oo.default_sheet = oo.sheets.first
2.upto(47) do |line|
data_a = [oo.cell(line,'B')]
data_b = [oo.cell(line,'E')]
chart_a = Gchart.new( :type => 'line',
:title => "A",
:theme => :keynote,
:width => 600,
:height => 500,
:data => data_a,
:line_colors => 'e0440e',
:axis_with_labels => ['x', 'y'],
:axis_range => [[0,50,20], [0,3000,500]],
:filename => "tmp/chart_a.png")
chart_b = Gchart.new( :type => 'line',
:title => "B",
:theme => :keynote,
:width => 600,
:height => 500,
:data => data_b,
:line_colors => 'e62ae5',
:axis_with_labels => ['x', 'y'],
:axis_range => [[0,50,20], [0,3000,500]],
:filename => "tmp/chart_b.png")
# Record file in filesystem
chart_a.file
chart_b.file
end
This will get every cell's content of column B and E to be the argument :data alone. How to return it as an array? If roo can't return array, then is there any else gem do this?
there is a column method that returns values of a given column as an array. Calling oo.column(2) should return you values for column B. oo.column('B') might work also. haven't tested it.
I needed the row back as a hash to be compatible with the logic I used for FasterCSV. This will give you a hash of the first row as the key and current line as the value.
def row_from_excel(s, line)
row = {}
s.first_column.upto(s.last_column) do |col|
cell_name = s.cell(1, col)
logger.debug "************* #{col} => #{cell_name} => #{s.cell(line, col)}"
row[cell_name] = s.cell(line, col)
end
row
end
s = Excelx.new(path_to_file) # or Excel.new(path_to_file)
2.upto(s.last_row) do |line|
row = row_from_excel(s, line)
end

Most efficient way to format a hash of data?

I am using this array of hashes to do a batch insert into a mongo DB. Each hash was populated by parsing a text file so the formatting of fields are in an unpredictable format. It might look something like:
{date => "March 5", time => "05:22:21", first_name = "John", middle_initial = "JJ", ...}
And I would have a series of formatting functions. So maybe:
def format_date
..convert if needed..
end
def format_time
...
end
How would I go about calling the formatting functions on various records? I could see doing some kind of lambda call where I iterate through the hash and call a format_record_name function, but not all records will have formatting functions. For instance above the first_name record wouldn't need one. Any ideas?
Just keep a list of the keys that you do want to handle. You could even tie it to the transformation functions with a Hash:
transformations = {
:date => lambda {|date| whatever},
:time => lambda {|time| whatever}
}
transformations.default = lambda {|v| v}
data.map do |hash|
Hash[ hash.map {|key, val| transformations[key][val] } ]
end
Here's one idea, pretty similar to what you stated. You might just have an identity function for the fields you don't want to format
def pass(x)
x
end
method_hash = {:date=>method(:your_format_date)}
method_hash.default = method(:pass)
x = {:date => "March 5", :time => "05:22:21", :first_name => "John", :middle_initial => "JJ"}
x.reduce({}) { |hsh,k| hsh[k[0]] = method_hash[k[0]].call(k[1]); hsh }
Make use of Ruby's Singleton (or Eigen) class and then the following one liner solves your problem:
module Formatter
def format_date
Date.parse(self[:date]).strftime('%Y-%m-%d')
end
def format_time
self[:time].split(':')[0,2].join('-')
end
def format_first_name
self[:first_name].upcase
end
def format
{:date => format_date, :time => format_time, :first_name => format_first_name, :last_name => self[:last_name]}
end
end
records = [
{:date => 'March 05', :time => '12:13:00', :first_name => 'Wes', :last_name => 'Bailey'},
{:date => 'March 06', :time => '09:15:11', :first_name => 'Joe', :last_name => 'Buck'},
{:date => 'March 07', :time => '18:35:48', :first_name => 'Troy', :last_name => 'Aikmen'},
]
records.map {|h| h.extend(Formatter).format}
=> [{:date=>"2011-03-05", :time=>"12-13", :first_name=>"WES", :last_name=>"Bailey"},
{:date=>"2011-03-06", :time=>"09-15", :first_name=>"JOE", :last_name=>"Buck"},
{:date=>"2011-03-07", :time=>"18-35", :first_name=>"TROY", :last_name=>"Aikmen"}]
class Formatters
def self.time(value)
"FORMATTED TIME"
end
def self.date(value)
"FORMATTED DATE"
end
def self.method_missing(name, arg)
arg
end
end
your_data = [{:date => "March 5", :time => "05:22:21", :first_name => "John", :middle_initial => "JJ"},
{:date => "March 6", :time => "05:22:22", :first_name => "Peter", :middle_initial => "JJ"},
{:date => "March 7", :time => "05:22:23", :first_name => "Paul", :middle_initial => "JJ"}]
formatted_data = your_data.map do |item|
Hash[ *item.map { |k, v| [k, Formatters.send(k, v)] }.flatten ]
end

Resources