My respect to community!
I have deep nested hash and I want to transform all values of specific key provided. Something like deep transform values if key == :something
Example of hash:
{:id=>"11ed35b8e53c442ea210c39d6f24bddf",
:createdAt=>"2022-09-16T12:12:55.454Z",
:updatedAt=>"2022-09-16T12:12:55.454Z",
:status=>"ACTIVE",
:description=>"test",
:goals=>
[{:Definitions=>[],
:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
:status=>"ACTIVE",
:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
:id=>"11e818be2f0157329c76634ee23c1d8f"}],
:healthConcernDefinitions=>[]}
I want to transform all values of all keys :status
Result must be:
{:id=>"11ed35b8e53c442ea210c39d6f24bddf",
:createdAt=>"2022-09-16T12:12:55.454Z",
:updatedAt=>"2022-09-16T12:12:55.454Z",
:status=>"TRANSFORMED",
:description=>"test",
:goals=>
[{:Definitions=>[],
:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
:status=>"TRANSFORMED",
:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"11ec1fc4bd36f876963867013cee2799"}],
:id=>"11e818be2f0157329c76634ee23c1d8f"}],
:healthConcernDefinitions=>[]}
I have a couple solutions
First one:
hash_to_json = hash.to_json.gsub(/"ACTIVE"|"INACTIVE"/, '"TRANSFORMED"')
JSON.parse(hash_to_json)
Second one:
hash.deep_transform_values { |v| (v == 'ACTIVE' || v == 'INACTIVE') ? 'TRANSFORMED' : v }
Both solutions are working but I do not like to match values, I want to match only specific key and change it all over the hash, somting like:
hash.deep_transform_keys { |k, v| v = 'TRANSFORMED' if k == :status }
Thanks a lot!!!
Unfortunately Hash#deep_transform_values takes in block only values, not keys. And unfortunately Hash#deep_merge and Hash#deep_merge! are not so powerful
You can write your own method(s). It is just idea, you can improve it
class Hash
def deep_replace(hash)
deep_dup.deep_replace!(hash)
end
def deep_replace!(hash)
each_key do |key|
hash.each do |k, v|
self[key] = v if key == k
end
_replace_object!(self[key], hash)
end
end
private
def _replace_object!(object, hash)
case object
when Hash
object.deep_replace!(hash)
when Array
object.map! { |v| _replace_object!(v, hash) }
else
object
end
end
end
hash = {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
:createdAt=>"2022-09-16T12:12:55.454Z",
:updatedAt=>"2022-09-16T12:12:55.454Z",
:status=>"ACTIVE",
:description=>"test",
:goals=>
[{:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
:status=>"ACTIVE",
:id=>"11e818be2f0157329c76634ee23c1d8f"}],
:healthConcernDefinitions=>[]}
And after that you can apply it
# Replace one key
hash.deep_replace(status: "TRANSFORMED")
# => {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
# [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"11ec1fc4bd36f876963867013cee2799"}],
# :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"TRANSFORMED",
# :id=>"11e818be2f0157329c76634ee23c1d8f"}],
# :healthConcernDefinitions=>[]}
# Replace few keys
hash.deep_replace(status: "TRANSFORMED", txid: "555", id: "99999999999999999999999999999999")
# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
# [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
# :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"TRANSFORMED",
# :id=>"99999999999999999999999999999999"}],
# :healthConcernDefinitions=>[]}
# Check original (it wasn't changed)
hash
# => {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"ACTIVE",
# :description=>"test",
# :goals=>
# [{:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
# :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"ACTIVE",
# :id=>"11e818be2f0157329c76634ee23c1d8f"}],
# :healthConcernDefinitions=>[]}
# Destructive method
hash.deep_replace!(status: "TRANSFORMED", id: "99999999999999999999999999999999")
# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
# [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
# :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"TRANSFORMED",
# :id=>"99999999999999999999999999999999"}],
# :healthConcernDefinitions=>[]}
# Check original (it was changed)
hash
# => {:id=>"99999999999999999999999999999999",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
# [{:Definitions=>[{:text=>"Search for relics", :status=>"TRANSFORMED", :id=>"99999999999999999999999999999999"}],
# :text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"TRANSFORMED",
# :id=>"99999999999999999999999999999999"}],
# :healthConcernDefinitions=>[]}
We could implement a conditional overwrite of a key using an implementation akin to deep_transform_keys.
def deep_overwrite(object, other_h)
case object
when Hash
object.each_with_object({}) do |(key, value), result|
result[key] = other_h[key] || deep_overwrite(value, other_h)
end
when Array
object.map { |e| deep_overwrite(e,other_h) }
else
object
end
end
While this does not take a block format (as shown in your post), that did not seems to be necessary based on your use case. Instead this will simply overwrite the value of any key (nested at any level) contained in the original (object) that is also contained in other_h.
To call this in your case
original = {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
:createdAt=>"2022-09-16T12:12:55.454Z",
:updatedAt=>"2022-09-16T12:12:55.454Z",
:status=>"ACTIVE",
:description=>"test",
:goals=>
[{:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
:status=>"ACTIVE",
:Definitions=>[{:text=>"Search for relics", :status=>"INACTIVE", :id=>"11ec1fc4bd36f876963867013cee2799"}],
:id=>"11e818be2f0157329c76634ee23c1d8f"}],
:healthConcernDefinitions=>[]}
result = deep_overwrite(original,{status: 'TRANSFORMED'})
#=> {:id=>"11ed35b8e53c442ea210c39d6f24bddf",
# :createdAt=>"2022-09-16T12:12:55.454Z",
# :updatedAt=>"2022-09-16T12:12:55.454Z",
# :status=>"TRANSFORMED",
# :description=>"test",
# :goals=>
# [{:text=>"Follows Action Plan Appropriately, Worse or Warning Symptoms",
# :status=>"TRANSFORMED",
# :Definitions=>
# [{:text=>"Search for relics",
# :status=>"TRANSFORMED",
# :id=>"11ec1fc4bd36f876963867013cee2799"}],
# :id=>"11e818be2f0157329c76634ee23c1d8f"}],
# :healthConcernDefinitions=>[]}
Related
I'd like to track symbol created during code loading in Ruby. I was planning to monkey patch Kernel#require and diff Symbol.all_symbols after and before calling to the built-in #require:
In loaded.rb
def create_symbols
s1 = :static_literal
s2 = :"symbol with interpolation #{s1}"
s3 = "dynamic symbol".to_sym
end
In main.rb
$new = []
def patch
Kernel.module_eval do
alias_method :org_require, :require
define_method :require do |path|
before = Symbol.all_symbols
r = org_require path
$new << Symbol.all_symbols - before
r
end
end
patch
# #require can be called in other threads as well, e.g. in a Rails app. This is just an example
require "./loaded.rb"
$new will only include :static_literal, which is what I need. But the problem is that the patch isn't threadsafe:
before = Symbol.all_symbols
# calling create_symbols() in between will alter all_symbols and leads to s2 and s3 added to all_symbols which I don't want to track
r = org_require path
$new << Symbol.all_symbols - before
Is there a thread safe way to achieve it?
I have a YAML file that is going to be parsed by two different machines, so I want to specify some sort of markers in the file to indicate which machine has the right to read a specific block.
As an example I want the block 1 to be parsed by machine 1 and block 2 to be parsed by machine2:
# BLOCK 1 - Machine 1
-
:id: 1234
:worker: Foo1
:opts:
:ftpaccount: user1
:limit: 10
# BLOCK 2 - Machine 2
-
:id: 5678
:worker: Foo2
:opts:
:ftpaccount: user2
:limit: 10
How can I achieve something like this? How you implement something similar to this? Thanks.
Treat the blocks as hash entries with the key being the hostname:
require 'yaml'
yaml = <<EOT
host1:
# BLOCK 1 - Machine 1
-
:id: 1234
:worker: Foo1
:opts:
:ftpaccount: user1
:limit: 10
host2:
# BLOCK 2 - Machine 2
-
:id: 5678
:worker: Foo2
:opts:
:ftpaccount: user2
:limit: 10
EOT
config = YAML.load(yaml)
# => {"host1"=>
# [{:id=>1234,
# :worker=>"Foo1",
# :opts=>{:ftpaccount=>"user1", :limit=>10}}],
# "host2"=>
# [{:id=>5678,
# :worker=>"Foo2",
# :opts=>{:ftpaccount=>"user2", :limit=>10}}]}
At this point you can grab the chunk you need:
config['host1']
# => [{:id=>1234, :worker=>"Foo1", :opts=>{:ftpaccount=>"user1", :limit=>10}}]
config['host2']
# => [{:id=>5678, :worker=>"Foo2", :opts=>{:ftpaccount=>"user2", :limit=>10}}]
You don't even have to hard-code the hostname; You can ask the machine what its name is:
`hostname`.chomp # => "MyHost"
Actually, I'd change the YAML a little, so it's a hash of hashes. As is, your YAML returns a hash of arrays of hashes, which, because of the array, makes it more awkward to use:
host1:
# BLOCK 1 - Machine 1
:id: 1234
:worker: Foo1
:opts:
:ftpaccount: user1
:limit: 10
host2:
# BLOCK 2 - Machine 2
:id: 5678
:worker: Foo2
:opts:
:ftpaccount: user2
:limit: 10
Results in:
config = YAML.load(yaml)
# => {"host1"=>
# {:id=>1234, :worker=>"Foo1", :opts=>{:ftpaccount=>"user1", :limit=>10}},
# "host2"=>
# {:id=>5678, :worker=>"Foo2", :opts=>{:ftpaccount=>"user2", :limit=>10}}}
config['host1']
# => {:id=>1234, :worker=>"Foo1", :opts=>{:ftpaccount=>"user1", :limit=>10}}
config['host2']
# => {:id=>5678, :worker=>"Foo2", :opts=>{:ftpaccount=>"user2", :limit=>10}}
Finally, if your YAML file is complex, or long, or has repeated sections, seriously consider writing code that emits that file for you. Ruby makes it really easy to generate the YAML in a very smart way that automatically uses aliases. For instance:
require 'yaml'
SOME_COMMON_DATA = {
'shared_db_dsn' => 'mysql://user:password#host/db'
}
HOST1 = 'foo.com'
HOST1_DATA = {
HOST1 => {
'id' => 1234,
'worker' => 'Foo1',
'opts' => {
'ftpaccount' => 'user1',
'limit' => 10
},
'dsn' => SOME_COMMON_DATA
}
}
HOST2 = 'bar.com'
HOST2_DATA = {
HOST2 => {
'id' => 5678,
'worker' => 'Foo2',
'opts' => {
'ftpaccount' => 'user2',
'limit' => 10
},
'dsn' => SOME_COMMON_DATA
}
}
data = {
HOST1 => HOST1_DATA,
HOST2 => HOST2_DATA,
}
puts data.to_yaml
# >> ---
# >> foo.com:
# >> foo.com:
# >> id: 1234
# >> worker: Foo1
# >> opts:
# >> ftpaccount: user1
# >> limit: 10
# >> dsn: &1
# >> shared_db_dsn: mysql://user:password#host/db
# >> bar.com:
# >> bar.com:
# >> id: 5678
# >> worker: Foo2
# >> opts:
# >> ftpaccount: user2
# >> limit: 10
# >> dsn: *1
Notice how YAML converted "dsn" into an alias and referenced it in the second host's definition using an anchor. This can add up to serious space savings, depending on how you define your variables and build the data structure. See "Aliases and Anchors" for more information.
Also, I'd highly recommend avoiding the use of symbols for your hash keys. By doing so your YAML can be easily loaded by other languages, not just Ruby. At that point, your YAML becomes even more useful when building big systems.
Here's a simple state machine that assembles a string based on the most recent matching comment in the yaml file. The YAML string is then loaded into the parser. If your files are really large, you could easily modify this to use Tempfile or some other IO class.
require 'yaml'
class YAMLSplitter
attr_reader :flag, :mode, :raw
def initialize(flag)
#flag = flag
#mode = :match
#raw = ""
end
def parse(file)
File.read(file).each_line do |line|
process_line(line)
end
YAML.load(raw)
end
private
def process_line(line)
set_match_status(line)
write_line(line) if match?
end
def set_match_status(line)
if line.start_with?("#")
if line.match(flag)
match!
else
nomatch!
end
end
end
def write_line(line)
puts "WRITE_LINE #{mode.inspect} #{line.inspect}"
raw << line
end
def match?
mode == :match
end
def match!
#mode = :match
end
def nomatch!
#mode = :nomatch
end
end
YAML:
---
# machine 1
- 1
- 2
- 3
- 4
# machine 2
- 5
- 6
- 7
- 8
- 9
- 10
- 11
# machine 1
- 12
Execution:
splitter = YAMLSplitter.new('machine 1')
yaml = splitter.parse('test.yml')
I want to make a simple change to the Guard Cucumber Notification Formatter to pass a priority, a bit like Guard Rspec does, so that growl styling can be improved. Small thing really.
I have tried this as a monkey patch in an initializer file but it won't work. I've tried all kinds of things but for whatever reason I cannot get it to recognize my monkey-patch when running the tests. No change I make in the patch seems to make a difference. I've tried all kinds of variants on the namespacing in case I'm making an error there - and I think that's most likely. Here's what I'm trying to apply:
initializers/guard_cucumber_patch.rb
require 'guard'
require 'guard/notifier'
require 'cucumber/formatter/console'
require 'cucumber/formatter/io'
module Guard
class Cucumber
class NotificationFormatter
def step_name(keyword, step_match, status, source_indent, background, file_colon_line)
if [:failed, :pending, :undefined].index(status)
#rerun = true
step_name = step_match.format_args(lambda { |param| "*#{ param }*" })
::Guard::Notifier.notify(step_name,
:title => #feature_name,
:image => icon_for(status),
:priority => priority(icon_for(status)))
end
end
# Just stolen from the guard/rspec/formatter code
def priority(image)
{ :failed => 2,
:pending => -1,
:success => -2
}[image]
end
end
end
end
The original guard/cucumber/notification_formatter.rb file is as follows:
require 'guard'
require 'guard/notifier'
require 'cucumber/formatter/console'
require 'cucumber/formatter/io'
module Guard
class Cucumber
# The notification formatter is a Cucumber formatter that Guard::Cucumber
# passes to the Cucumber binary. It writes the `rerun.txt` file with the failed features
# an creates system notifications.
#
# #see https://github.com/cucumber/cucumber/wiki/Custom-Formatters
#
class NotificationFormatter
include ::Cucumber::Formatter::Console
attr_reader :step_mother
# Initialize the formatter.
#
# #param [Cucumber::Runtime] step_mother the step mother
# #param [String, IO] path_or_io the path or IO to the feature file
# #param [Hash] options the options
#
def initialize(step_mother, path_or_io, options)
#options = options
#file_names = []
#step_mother = step_mother
end
# Notification after all features have completed.
#
# #param [Array[Cucumber::Ast::Feature]] features the ran features
#
def after_features(features)
notify_summary
write_rerun_features if !#file_names.empty?
end
# Before a feature gets run.
#
# #param [Cucumber::Ast::FeatureElement] feature_element
#
def before_feature_element(feature_element)
#rerun = false
#feature_name = feature_element.name
end
# After a feature gets run.
#
# #param [Cucumber::Ast::FeatureElement] feature_element
#
def after_feature_element(feature_element)
if #rerun
#file_names << feature_element.file_colon_line
#rerun = false
end
end
# Gets called when a step is done.
#
# #param [String] keyword the keyword
# #param [Cucumber::StepMatch] step_match the step match
# #param [Symbol] status the status of the step
# #param [Integer] source_indent the source indentation
# #param [Cucumber::Ast::Background] background the feature background
# #param [String] file name and line number describing where the step is used
#
def step_name(keyword, step_match, status, source_indent, background, file_colon_line)
if [:failed, :pending, :undefined].index(status)
#rerun = true
step_name = step_match.format_args(lambda { |param| "*#{ param }*" })
::Guard::Notifier.notify step_name, :title => #feature_name, :image => icon_for(status)
end
end
private
# Notify the user with a system notification about the
# result of the feature tests.
#
def notify_summary
icon, messages = nil, []
[:failed, :skipped, :undefined, :pending, :passed].reverse.each do |status|
if step_mother.steps(status).any?
step_icon = icon_for(status)
icon = step_icon if step_icon
messages << dump_count(step_mother.steps(status).length, 'step', status.to_s)
end
end
::Guard::Notifier.notify messages.reverse.join(', '), :title => 'Cucumber Results', :image => icon
end
# Writes the `rerun.txt` file containing all failed features.
#
def write_rerun_features
File.open('rerun.txt', 'w') do |f|
f.puts #file_names.join(' ')
end
end
# Gives the icon name to use for the status.
#
# #param [Symbol] status the cucumber status
# #return [Symbol] the Guard notification symbol
#
def icon_for(status)
case status
when :passed
:success
when :pending, :undefined, :skipped
:pending
when :failed
:failed
else
nil
end
end
end
end
end
EDIT: I am using jruby-head if that makes a difference to monkey business.
Guard::Cucumber makes a system call to start Cucumber in a subshell and your monkey patch is not loaded in that environment. You need to tell Cucumber to require your patch on start like the notification formatter is required.
I'm not actively developing Guard::Cucumber at the moment because I have no project with Cucumber, but I still do maintain it and I'll happily merge that improvement if you make a pull request. I think your improvement would be useful for other users as well.
try to put this file in spec/support dir (create if not exists)
this dir contents are usually included in spec/spec_helper.rb just before running any tests
I am using Prawn pdf library however i am doing a complex design, So I need a quick solution as to convert a html to pdf file.
Thanks in advance
I would use wkhtmltopdf shell tool
together with the wicked_pdf ruby gem, its free, and uses qtwebkit to render your html to pdf. Also executes javascript as well, for charts for example. You can find more info about installation: https://github.com/mileszs/wicked_pdf
I have one Rails app that's been using PrinceXML in production for a few years. It is pricey - around $4K for a server license - but does a very good job of rendering HTML+CSS in PDF files. I haven't looked at newer solutions since this one is paid for and working quite well.
For what it's worth, here's some code I adapted from Subimage Interactive to make the conversion simple:
lib/prince.rb
# Prince XML Ruby interface.
# http://www.princexml.com
#
# Library by Subimage Interactive - http://www.subimage.com
#
#
# USAGE
# -----------------------------------------------------------------------------
# prince = Prince.new()
# html_string = render_to_string(:template => 'some_document')
# send_data(
# prince.pdf_from_string(html_string),
# :filename => 'some_document.pdf'
# :type => 'application/pdf'
# )
#
class Prince
attr_accessor :exe_path, :style_sheets, :log_file
# Initialize method
#
def initialize()
# Finds where the application lives, so we can call it.
#exe_path = '/usr/local/bin/prince'
case Rails.env
when 'production', 'staging'
# use default hard-coded path
else
if File.exist?(#exe_path)
# use default hard-coded path
else
#exe_path = `which prince`.chomp
end
end
#style_sheets = ''
#log_file = "#{::Rails.root}/log/prince.log"
end
# Sets stylesheets...
# Can pass in multiple paths for css files.
#
def add_style_sheets(*sheets)
for sheet in sheets do
#style_sheets << " -s #{sheet} "
end
end
# Returns fully formed executable path with any command line switches
# we've set based on our variables.
#
def exe_path
# Add any standard cmd line arguments we need to pass
#exe_path << " --input=html --server --log=#{#log_file} "
#exe_path << #style_sheets
return #exe_path
end
# Makes a pdf from a passed in string.
#
# Returns PDF as a stream, so we can use send_data to shoot
# it down the pipe using Rails.
#
def pdf_from_string(string)
path = self.exe_path()
# Don't spew errors to the standard out...and set up to take IO
# as input and output
path << ' --silent - -o -'
# Show the command used...
#logger.info "\n\nPRINCE XML PDF COMMAND"
#logger.info path
#logger.info ''
# Actually call the prince command, and pass the entire data stream back.
pdf = IO.popen(path, "w+")
pdf.puts(string)
pdf.close_write
output = pdf.gets(nil)
pdf.close_read
return output
end
end
lib/pdf_helper.rb
module PdfHelper
require 'prince'
private
def make_pdf(template_path, pdf_name, stylesheets = [], skip_base_pdf_stylesheet = false)
# application notices should never be included in PDFs, pull them from session here
notices = nil
if !flash.now[:notice].nil?
notices = flash.now[:notice]
flash.now[:notice] = nil
end
if !flash[:notice].nil?
notices = '' if notices.nil?
notices << flash[:notice]
flash[:notice] = nil
end
prince = Prince.new()
# Sets style sheets on PDF renderer.
stylesheet_base = "#{::Rails.root}/public/stylesheets"
prince.add_style_sheets(
"#{stylesheet_base}/application.css",
"#{stylesheet_base}/print.css"
)
prince.add_style_sheets("#{stylesheet_base}/pdf.css") unless skip_base_pdf_stylesheet
if 0 < stylesheets.size
stylesheets.each { |s| prince.add_style_sheets("#{stylesheet_base}/#{s}.css") }
end
# Set RAILS_ASSET_ID to blank string or rails appends some time after
# to prevent file caching, messing up local - disk requests.
ENV['RAILS_ASSET_ID'] = ''
html_string = render_to_string(:template => template_path, :layout => 'application')
# Make all paths relative, on disk paths...
html_string.gsub!("src=\"", "src=\"#{::Rails.root}/public")
html_string.gsub!("src=\"#{::Rails.root}/public#{::Rails.root}", "src=\"#{::Rails.root}")
# re-insert any application notices into the session
if !notices.nil?
flash[:notice] = notices
end
return prince.pdf_from_string(html_string)
end
def make_and_send_pdf(template_path, pdf_name, stylesheets = [], skip_base_pdf_stylesheet = false)
send_data(
make_pdf(template_path, pdf_name, stylesheets, skip_base_pdf_stylesheet),
:filename => pdf_name,
:type => 'application/pdf'
)
end
end
sample controller action
include PdfHelper
def pdf
index
make_and_send_pdf '/ads/index', "#{filename}.pdf"
end
You can directly convert your existing HTML to PDF using acts_as_flying_saucer library.For header and footer you can refer
https://github.com/amardaxini/acts_as_flying_saucer/wiki/PDF-Header-Footer
Hey guys,
i have 5 model attributes, for example, 'str' and 'dex'. A user has strength, dexterity attribute.
When i call user.increase_attr('dex') i want to do it through 'dex' and not having to pass 'dexterity' string all the way.
Of course, i can just check if ability == 'dex' and convert it to 'dexterity' when i will need to do user.dexterity += 1 and then save it.
But what is a good ruby way to do that ?
Look at Ruby's Abbrev module that's part of the standard library. This should give you some ideas.
require 'abbrev'
require 'pp'
class User
def increase_attr(s)
"increasing using '#{s}'"
end
end
abbreviations = Hash[*Abbrev::abbrev(%w[dexterity strength speed height weight]).flatten]
user = User.new
user.increase_attr(abbreviations['dex']) # => "increasing using 'dexterity'"
user.increase_attr(abbreviations['s']) # => "increasing using ''"
user.increase_attr(abbreviations['st']) # => "increasing using 'strength'"
user.increase_attr(abbreviations['sp']) # => "increasing using 'speed'"
If an ambiguous value is passed in, (the "s"), nothing will match. If a unique value is found in the hash, the returned value is the full string, making it easy to map short strings to the full string.
Because having varying lengths of the trigger strings would be confusing to the user you could strip all elements of the hash that have keys shorter than the shortest unambiguous key. In other words, remove anything shorter than two characters because of the collision of "speed" ("sp") and "strength" ("st"), meaning "h", "d" and "w" need to go. It's a "be kind to the poor human users" thing.
Here's what is created when Abbrev::abbrev does its magic and it's coerced into a Hash.
pp abbreviations
# >> {"dexterit"=>"dexterity",
# >> "dexteri"=>"dexterity",
# >> "dexter"=>"dexterity",
# >> "dexte"=>"dexterity",
# >> "dext"=>"dexterity",
# >> "dex"=>"dexterity",
# >> "de"=>"dexterity",
# >> "d"=>"dexterity",
# >> "strengt"=>"strength",
# >> "streng"=>"strength",
# >> "stren"=>"strength",
# >> "stre"=>"strength",
# >> "str"=>"strength",
# >> "st"=>"strength",
# >> "spee"=>"speed",
# >> "spe"=>"speed",
# >> "sp"=>"speed",
# >> "heigh"=>"height",
# >> "heig"=>"height",
# >> "hei"=>"height",
# >> "he"=>"height",
# >> "h"=>"height",
# >> "weigh"=>"weight",
# >> "weig"=>"weight",
# >> "wei"=>"weight",
# >> "we"=>"weight",
# >> "w"=>"weight",
# >> "dexterity"=>"dexterity",
# >> "strength"=>"strength",
# >> "speed"=>"speed",
# >> "height"=>"height",
# >> "weight"=>"weight"}
def increase_attr(attr)
attr_map = {'dex' => :dexterity, 'str' => :strength}
increment!(attr_map[attr]) if attr_map.include?(attr)
end
Basically create a Hash that has the key of 'dex', 'str' etc and points to the expanded version of that word(in symbol format).