How to specify markers in YAML file - ruby-on-rails

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')

Related

Local jekyll cannot load unicode urls

My Local Jekyll's version is 4.0.0. and OS is Windows 10
I wanna access to 'http://localhost:4000/문제풀이/문제풀이/', but I can't. only shows 404page.
On the other hand, github pages's blog url 'https://neomindstd.github.io/문제풀이/문제풀이/' is accessible.
Below is Yaml Front Matter of '2020-02-11-문제풀이.md'
---
title: "문제풀이 테스트"
excerpt: "테스트다"
categories:
- 문제풀이
tags:
- Blog
date: 2020-02-09 KST 04:34 +0900
toc: true
toc_sticky: true
toc_label: "목차"
published: true
---
part of _config.yml
# Outputting
permalink: /:categories/:title/
paginate: 5 # amount of posts to show
paginate_path: /page:num/
timezone: Asia/Seoul # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
so, created: .site/문제풀이/문제풀이/index.html
I've tried solution.
add the _plugins/post.rb.
# https://github.com/jekyll/jekyll-help/issues/129#issuecomment-61255284
# https://stackoverflow.com/questions/41941320/jekyll-encoding-name-of-category-special-characters
# _plugins/post.rb
module Jekyll
class Post
# override post method in order to return categories names as slug
# instead of strings
#
# An url for a post with category "category with space" will be in
# slugified form : /category-with-space
# instead of url encoded form : /category%20with%20space
#
# #see utils.slugify
def url_placeholders
{
:year => date.strftime("%Y"),
:month => date.strftime("%m"),
:day => date.strftime("%d"),
:title => slug,
:i_day => date.strftime("%-d"),
:i_month => date.strftime("%-m"),
:categories => (categories || []).map { |c| Utils.slugify(c) }.join('/'),
:short_month => date.strftime("%b"),
:short_year => date.strftime("%y"),
:y_day => date.strftime("%j"),
:output_ext => output_ext
}
end
end
end
but It doesn't work.
So, how to access unicode url on local?

`Apartment::Tenant.switch!` during `bin/rails console` using `pry`

when console is launched
while at console prompt
How it should work?
See the output here. Simple, quick methods. T.me (current tenant), T.names (tenants in the DB), ...
Launch, ask for tenant selection, set
$ bin/rails c
Running via Spring preloader in process 11233
Loading development environment (Rails 5.1.5)
(1.9ms) SELECT "public"."tenants"."subdomain" FROM "public"."tenants" WHERE "public"."tenants"."deleted_at" IS NULL ORDER BY "public"."tenants"."created_at" DESC
Available tenants: {0=>"public", 1=>"local"}
Select tenant: 1
You are now Tenant 'local'
Frame number: 0/24
Switch tenant
[1] [my-project][development] pry(main)> T.ask
Available tenants: {0=>"public", 1=>"local"}
Select tenant: 0
You are now Tenant 'public'
=> nil
Switch again
[2] [my-project][development] pry(main)> T.ask
Available tenants: {0=>"public", 1=>"local"}
Select tenant: 1
You are now Tenant 'local'
=> nil
Current tenant
[3] [my-project][development] pry(main)> T.me
=> "local"
Tenant we can quickly switch to
[4] [my-project][development] pry(main)> T.hash
=> {0=>"public", 1=>"local"}
Tenant names
[5] [my-project][development] pry(main)> T.names
=> ["local"]
Is abc a tenant?
[6] [my-project][development] pry(main)> T.exists? 'abc'
=> false
Is local a tenant?
[7] [my-project][development] pry(main)> T.exists? 'local'
=> true
Note: This is not tested thoroughly. Please test before using. This code just gives you some idea, how I have been using these small shortcuts to save time during development. Thank you for reading.
Put it inside <project-root>/.pryrc
# What is it?
# => Helper methods for Apartment::Tenant gem
# How does it work?
# * bin/rails console => auto-loads and asks to switch tenant
# * T.ask => anytime in console, to switch tenant from a list
# * T.me => same as Apartment::Tenant.current
# * T.hash => hash of tenants. Example: { 0 => "public", 1 => "tenant-a" }
# * T.names => array with all existing tenant names from DB
# * T.exists?(arg) => returns true/false if `arg` exists as tenant in DB
# * T.switch!(arg) => same as Apartment::Tenant.switch!
require "rubygems"
# convenience class
class T
class << self
# ['tenant1', 'tenant2', ...]
def names
##names ||= Apartment.tenant_names.sort
end
# { 0 => 'public', 1 => 'tenant1', ...}
def hash
##hash ||= { 0 => 'public' }.merge(
(1..(T.names.length)).to_a
.product(T.names)
.to_h
)
end
def switch! arg
Apartment::Tenant.switch!(arg) if T.hash.value?(arg)
end
# current tenant
def me
Apartment::Tenant.current
end
def exists? arg
T.names.include? arg
end
# ask to switch the tenant
def ask
WelcomeClass.select_tenant
end
end
end
# select tenant when entering console
class WelcomeClass
def self.select_tenant
puts "Available tenants: #{T.hash}"
print "Select tenant: "
tenant = gets.strip # ask which one?
unless tenant.empty?
# by name
if T.exists?(tenant)
T.switch!(tenant)
# by index position
# string has digit + tenant index present
elsif tenant[/\d/].present? && T.hash.key?(tenant.to_i)
T.switch!(T.hash[tenant.to_i])
# not found = no action
else
puts "Tenant not found in list '#{tenant}'"
end
end
# announce current tenant
puts "You are now Tenant '#{T.me}'"
end
end
# run the code at `bin/rails console`
Pry.config.exec_string = WelcomeClass.select_tenant
An update is needed for the accepted answer: the T 'hash' method is creating a hash with the right number of keys but the values for all keys are duplicated with the last tenant name (0 => 'public', 1 => 'test', 2 => 'test' .. x => 'test'). Here's a working 'hash' method:
def hash
##hash ||= Hash[(0..T.names.size - 1).zip T.names]
end
bazfer answer is partially correct, it was forgotten public tenant
def hash
##hash ||= { 0 => 'public' }.merge(Hash[(1..T.names.size).zip T.names])
end
Please add to bazfer answer and to accepted answer

Date range in ruby/rails

I want to get date range between from and till in Rails seed.
When I try to generate date range ((Date.today - 10)..Date.today) exception occurred.
Exception message: bad value for range
But in the Rails Console everything all right.
I think ActiveSupport are reasonable for that (my debugger told me that).
Ralls 3.1.3
What's going on?
You can understand what's going on by splitting the two edges and check their class like so:
Date.today.class # => Date
(Date.today - 10).class # => Date
((Date.today - 10)..Date.today).each {|d| puts d.class} # => 10 Date works for me
The error you're experiencing is something like this:
('a'..10) # => ArgumentError: bad value for range
Can you post the classes of your 2 edges of the range?
(Date.today - 10).class => ?
Date.today.class => ?
Have you overwritten any class in your rails environment? Does it work in irb?
PS: As you're in rails you can use 10.days.ago but you'll need to use to_date as it's a ActiveSupport::TimeWithZone
begin
((Date.today - 10)..Date.today).each { |date| puts date }
rescue
$! # => #<NameError: uninitialized constant Date>
end
require 'date'
((Date.today - 10)..Date.today).each { |date| puts date }
# >> 2012-04-06
# >> 2012-04-07
# >> 2012-04-08
# >> 2012-04-09
# >> 2012-04-10
# >> 2012-04-11
# >> 2012-04-12
# >> 2012-04-13
# >> 2012-04-14
# >> 2012-04-15
# >> 2012-04-16

Map string to another string in Ruby Rails

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).

Is it possible to simulate the behaviour of sprintf("%g") using the Rails NumberHelper methods?

sprintf("%g", [float]) allows me to format a floating point number without specifying precision, such that 10.00 is rendered as 10 and 10.01 is rendered as 10.01, and so on. This is neat.
In my application I'm rendering numbers using the Rails NumberHelper methods so that I can take advantage of the localization features, but I can't figure out how to achieve the above functionality through these helpers since they expect an explicit :precision option.
Is there a simple way around this?
Why not just use Ruby's Kernel::sprintf with NumberHelper? Recommended usage with this syntax: str % arg where str is the format string (%g in your case):
>> "%g" % 10.01
=> "10.01"
>> "%g" % 10
=> "10"
Then you can use the NumberHelper to print just the currency symbol:
>> foo = ActionView::Base.new
>> foo.number_to_currency(0, :format => "%u") + "%g"%10.0
=> "$10"
and define your own convenience method:
def pretty_currency(val)
number_to_currency(0, :format => "%u") + "%g"%val
end
pretty_currency(10.0) # "$10"
pretty_currency(10.01) # "$10.01"
I have solved this by adding another method to the NumberHelper module as follows:
module ActionView
module Helpers #:nodoc:
module NumberHelper
# Formats a +number+ such that the the level of precision is determined using the logic of sprintf("%g%"), that
# is: "Convert a floating point number using exponential form if the exponent is less than -4 or greater than or
# equal to the precision, or in d.dddd form otherwise."
# You can customize the format in the +options+ hash.
#
# ==== Options
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
#
# ==== Examples
# number_with_auto_precision(111.2345) # => "111.2345"
# number_with_auto_precision(111) # => "111"
# number_with_auto_precision(1111.2345, :separator => ',', :delimiter => '.') # "1,111.2345"
# number_with_auto_precision(1111, :separator => ',', :delimiter => '.') # "1,111"
def number_with_auto_precision(number, *args)
options = args.extract_options!
options.symbolize_keys!
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
begin
number_with_delimiter("%g" % number,
:separator => separator,
:delimiter => delimiter)
rescue
number
end
end
end
end
end
It is the specific call to number_with_delimiter with the %g option which renders the number as described in the code comments above.
This works great for me, but I'd welcome thoughts on this solution.

Resources