How do I... truncate string in an array - ruby

In a ruby on rails app, I build an array of Project Names and project id values, but want to truncate the length of the names. Current code is:
names = active_projects.collect {|proj| [proj.name, proj.id]}
I have tried to add a truncate function to the block, but am getting undefined method for class error.
Thanks in advance - I just cannot wrap my head around this yet.

Assuming I understood the question properly:
max_length = 10 # this is the length after which we will truncate
names = active_projects.map { |project|
name = project.name.to_s[0..max_length] # I am calling #to_s because the question didn't specify if project.name is a String or not
name << "…" if project.name.to_s.length > max_length # add an ellipsis if we truncated the name
id = project.id
[name, id]
}

Try Following
name=[]
active_projects.collect {|proj| name << [proj.name, proj.id]}
EDITED this should be
names= active_projects.collect {|proj| [proj.name.to_s[0..10], proj.id]}

In a Rails application you can use the truncate method for this.
If your code isn't in a view then you will need to include the TextHelper module in order to make the method available:
include ActionView::Helpers::TextHelper
you can then do:
names = active_projects.collect { |proj| [truncate(proj.name), proj.id] }
The default behaviour is to truncate to 30 characters and replace the removed characters with '...' but this can be overridden as follows:
names = active_projects.collect {
# truncate to 10 characters and don't use '...' suffix
|proj| [truncate(proj.name, :length => 10, :omission => ''), proj.id]
}

Related

pg gem: 'Warning: no type cast defined for type "numeric" '

I'm having trouble getting typed results out of the pg gem.
require 'pg'
require_relative 'spec/fixtures/database'
client = PG.connect( DB[:pg] )
client.type_map_for_queries = PG::BasicTypeMapForQueries.new(client)
client.type_map_for_results = PG::BasicTypeMapForResults.new(client)
client.exec( %|select * from testme;| ) do |query|
query.each {|r| puts r.inspect }
end
This program gives the output:
Warning: no type cast defined for type "money" with oid 790. Please cast this type explicitly to TEXT to be safe for future changes.
Warning: no type cast defined for type "numeric" with oid 1700. Please cast this type explicitly to TEXT to be safe for future changes.
{"string"=>"thing", "logical"=>true, "cash"=>"£1.23", "reel"=>"2.34", "day"=>#<Date: 2015-12-31 ((2457388j,0s,0n),+0s,2299161j)>, "float"=>3.45}
So: booleans and floats and dates (and integers) get converted, but not numerics or the money type.
Can anyone tell me how to "cast the type explicitly", assuming that I don't want to hard-code a solution for each table?
Hijacking this thread, as after some digging I finally found a way to add a custom decoder/encoder, so posting an example below:
require 'ipaddr'
require 'pg'
class InetDecoder < PG::SimpleDecoder
def decode(string, tuple=nil, field=nil)
IPAddr.new(string)
end
end
class InetEncoder < PG::SimpleEncoder
def encode(ip_addr)
ip_addr.to_s
end
end
# 0 if for text format, can also be 1 for binary
PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)
Here's a catch all for those seeking to cast strings by default:
client = PG.connect( DB[:pg] )
map = PG::BasicTypeMapForResults.new(conn)
map.default_type_map = PG::TypeMapAllStrings.new
client.type_map_for_results = map
Got the same problem with a text-ish field. Solved by duplicating a coder and editing its OID.
text_coder = client.type_map_for_results.coders.find { |c| c.name == 'text' }
new_coder = text_coder.dup.tap { |c| c.oid = 19 } # oid from the warning
conn.type_map_for_results.add_coder(new_coder)
How I got there: it might interest the next guy, if the problem is similar but not identical.
I read other people online talking about type_map_for_results, but how they didn't know how to define a coder. Since it was a text field in my case, I decided to try cloning an existing one. I knew I could find a textual pre-set in a Rails app, so I opened a rails console and searched:
adapter = ActiveRecord::Base.connection
connection = adapter.instance_variable_get("#connection")
mapping = connection.type_map_for_results
cd mapping # my console of choice is `pry`
ls # spotted a likely getter named `coders`
cd coders # again
ls # spotted getter `name` and setter `oid=`
So I put together the code in the solution. Gave it a try, and it worked.
It had not been straightforward to find, so I decided to exit lurker mode and share it on SO. Thereby: thanks #Andreyy for bringing me in :)
[pry cd and ls]
Google the error message: "Warning: no type cast defined for type"
You can find it's source github.
Reding the class, I would guess lines from 150 to 214 could be consiredered
examples:
register_type 0, 'text', PG::TextEncoder::String
alias_type 0, 'varchar', 'text'
Since register_type and alias_type are class methods of PG::BasicTypeRegistry::CoderMap I would play with them just and see if anything changes:
PG::BasicTypeRegistry::CoderMap.alias_type 0, 'money', 'text'
PG::BasicTypeRegistry::CoderMap.alias_type 0, 'numeric', 'text'
Reading the comments in the class it seems that the coding/decoding of those and some other fields is not implemented.
You might consider using a higher level ORM library like AvtiveRecord which implements more types (money).

Ruby: Interpolate strings in hash creation

I'm currently trying to write a small script which parses weekly reports(e-mails) and stores the data I want in variables, so I can handle them further.
The functionality is working already, but it's sort of hacked in at the moment and I'd like to learn how the code should look idealy.
This is my code at the moment - I shortened it a bit and made it more generic:
activated_accounts_rx = Regexp.new(/example/)
canceled_accounts_rx = Regexp.new(/example/)
converted_accounts_rx = Regexp.new(/example/)
File.open("weekly_report.txt") do |f|
input = f.read
activated_accounts = input.scan(activated_accounts_rx).join
canceled_accounts = input.scan(canceled_accounts_rx).join
converted_accounts = input.scan(converted_accounts_rx).join
end
I thought of something like this and I know that it can't work, but I don't know how I can get it to work:
var_names = ["activated_accounts",
"canceled_accounts",
"converted_accounts"]
regex = { "#{var_names[0]}": Regexp.new(/example/),
"#{var_names[1]}": Regexp.new(/example/) }
File.open("weekly_report.txt") do |f|
input = f.read
for name in var_names
name = input.scan(regex[:#{name}]).join
end
end
I would like to end up with variables like this:
activated_accounts = 13
canceled_accounts = 21
converted_accounts = 5
Can someone help me please?
You probably don't need to have a separate array for your variables. Just use them as the keys in the hash. Then you can access the values later from another hash. Better yet, if you don't need the regexes anymore, you can just replace that value with the scanned contents.
regexHash = {
activated_accounts: Regexp.new(/example/),
canceled_accounts : Regexp.new(/example/),
converted_accounts: Regexp.new(/example/)
}
values = {}
contents = File.open("weekly_report.txt").read
regexHash.each do |var, regex|
values[var] = contents.scan(regex).join
end
To access your values later just use
values[:var_name] # values[:activated_accounts] for example
If you want separate name's variable in an array, you can use to_sym:
regex = { var_names[0].to_sym => Regexp.new(/example/),
var_names[1].to_sym => Regexp.new(/example/) } #Rocket notation!
And:
for name in var_names
name = input.scan(regex[var_names[0].to_sym]).join
end
Anyway, i prefer the Rob Wagner advice.

Parse a string with multiple XML-like tags using Ruby

I have a string which looks like the following:
string = " <SET-TOPIC>INITIATE</SET-TOPIC>
<SETPROFILE>
<PROFILE-KEY>predicates_live</PROFILE-KEY>
<PROFILE-VALUE>yes</PROFILE-VALUE>
</SETPROFILE>
<think>
<set><name>first_time_initiate</name>yes</set>
</think>
<SETPROFILE>
<PROFILE-KEY>first_time_initiate</PROFILE-KEY>
<PROFILE-VALUE>YES</PROFILE-VALUE>
</SETPROFILE>"
My objective is to be able to read out each top level that is in caps with the parse. I use a case statement to evaluate what is the top level key, such as <SETPROFILE> but there can be lots of different values, and then run a method that does different things with the contnts of the tag.
What this means is I need to be able to know very easily:
top_level_keys = ['SET-TOPIC', 'SET-PROFILE', 'SET-PROFILE']
when I pass in the key know the full value
parsed[0].value = {:PROFILE-KEY => predicates_live, :PROFILE-VALUE => yes}
parsed[0].key = ['SET-TOPIC']
I currently parse the whole string as follows:
doc = Nokogiri::XML::DocumentFragment.parse(string)
parsed = doc.search('*').each_with_object({}){ |n, h|
h[n.name] = n.text
}
As a result, I only parse and know of the second tag. The values from the first tag do not show up in the parsed variable.
I have control over what the tags are, if that helps.
But I need to be able to parse and know the contents of both tag as a result of the parse because I need to apply a method for each instance of the node.
Note: the string also contains just regular text, both before, in between, and after the XML-like tags.
It depends on what you are going to achieve. The problem is that you are overriding hash keys by new values. The easiest way to collect values is to store them in array:
parsed = doc.search('*').each_with_object({}) do |n, h|
# h[n.name] = n.text :: removed because it overrides values
(h[n.name] ||= []) << n.text
end

Attempting to open the latest file with a wildcard then using include

I'm using the following to open a file within a directory that contains 'abc' in the filename. The wildcard is used as the rest of the filename includes a timestamp (time and date the file was created).
required = ["this", "two"]
log = Dir['*.log'].select { |f| f.include?('abc')}
result = required.all? do |phrase|
log.any? { |line| line.include?(phrase) }
end
index = result ? "Pass" : "Fail"
The above script is part of an automation process, so the full name will not be known (file created in one of the initial steps)
Is there a way to sort files by mtime prior to the .select?
I've tried .sort and .max_by prior to .select and get private method error
log = Dir['*.log'].max_by {|f| File.mtime(f)}.select { |f| f.include?('abc')}
I've tried it after .select, which gets the latest file but throws and underfined method 'any?' - this seems incorrect anyway as i should be sorting before selecting.
any assistance would be appreciated
#max_by is going to return at most one element. You should be able to use #sort_by to arrange the files according to their mtimes, #reverse this list, and then use #find (or its alias #detect) to pluck out the first element matching the predicate ({ |f| f[/abc/] }, in this case).

How do I specify to prawn to truncate table cell content?

I'd like to instruct Prawn to truncate the contents of table cells instead of wrapping them.
I've tried setting the style as follows, but it has no effect:
options = {
cell_style: {
overflow: :truncate
},
}
pdf.table(entries, options)
If there is no direct way to specify truncation, I need a workaround recipe.
Please consider the following:
I cannot truncate the strings themselves, because the font is not fixed-width
calculating the width of each string is acceptable, but I would also need a way to retrieve the column width.
A fixed height could be used, but only if the height of the text is fixed. Only the first line of the text will be printed.
Example:
pdf.table(entries, :cell_style => {:height => 25})
Another option is to use a custom wrapper. Details are here:
https://github.com/prawnpdf/prawn/blob/master/lib/prawn/text/formatted/box.rb#L149
Example:
module MyWrap
def wrap(array)
initialize_wrap(array)
#line_wrap.wrap_line(:document => #document,
:kerning => #kerning,
:width => available_width,
:arranger => #arranger)
if enough_height_for_this_line?
move_baseline_down
print_line
end
#text = #printed_lines.join("\n")
#everything_printed = #arranger.finished?
#arranger.unconsumed
end
end
Prawn::Text::Formatted::Box.extensions << MyWrap
entries = [ ["very long text here", "another very long text here"] ]
Prawn::Document.generate("test.pdf") do
table entries, :cell_style => { :inline_format => true }
end
I just copy the original wrap method and remove the while loop in order to print only the first line of the text.
Note that :inline_format => true must be used in order to get Prawn::Text::Formatted::Box working.
http://prawn.majesticseacreature.com/docs/0.11.1/Prawn/Text/Formatted.html
That has a way to truncate text in a box with
formatted_text_box(array, options={})
But it's not a table. I don't think there is any way to do it with a table, you'll have to use textboxes.

Resources