how can I read a YAML file? - ruby-on-rails

I have such a YAML file:
Company1:
name: Something1
established: 2000
#
Company2:
name: Something2
established: 1932
reading the YAML file: (** UPDATE **)
config = YAML.load_file('file.yaml')
config.each do |key, value|
if(key == 'name')
company_name = value
#year = config['Company1']['established']
year = config.fetch(key)['established']
end
end
** UPDATE **
Now the above code is working, but it shows the result as:
company1 => {"name" => "something1"} => {"established year" => 2000"}
how can I remove the the {} and "" ?

Okay, so this is your YAML file right?
Company1:
name: Something1
established: 2000
Company2:
name: Something2
established: 1932
Okay now this YAML file actually represents a Hash. The has has two keys i.e Company1, Company2 (because they are the leading entries and the sub entries (name and established) are indented under them). The value of these two keys is again a Hash. This Hash also has 2 keys namely name and established. And they have values like Something1 and 2000 respectively etc.
So when you do,
config=YAML.load_file('file.yml')
And print config (which is a Hash representing the YAML file contents) using,
puts config
you get following output:
{"Company1"=>{"name"=>"Something1", "established"=>2000}, "Company2"=>{"name"=>"Something2", "established"=>1932}}
So we have a Hash object as described by the YAML file.
Using this Hash is pretty straight forward.
Since each company's name and year come in a separate hash held by the outer hash (company1, company2), we can iterate through the companies. The following Code prints the Hash.
config.each do |company,details|
puts company
puts "-------"
puts "Name: " + details["name"]
puts "Established: " + details["established"].to_s
puts "\n\n"
end
So in Each iteration we get access to each (key,value) of the Hash. This in first iteration we have company(key) as Company1 and details(value) as {"name"=>"Something1", "established"=>2000}
Hope this helped.

YAML uses indentation for scoping, so try, e.g.:
Company1:
name: Something1
established: 2000
Company2:
name: Something2
established: 1932

Related

Retrieve data from YAML and convert each value as its own array

I have this in my yml file
members:
- id: 1
name: Sam
- id: 18
name: tom
After retrieving data from this file in a rails application I want to convert it to an array.
for example
id=[1,18]
name=[sam,tom]
how can I achieve this?
Currently This is how I am retrieving the data.
yml = YAML.load_file("mem.yml")
And this is how i get my data
[{"id":1, "name":"Sam"},{"id":18, "name":"tom"}]
if I use yml["members"][1]["id"] I get the first id.
I also tried writing id and name separately like below. This does give me what I want when I use yml["id"]but I don't want to use it because of its readability. BTW my data is static.
id:
- 1
- 18
name:
- Sam
- tom
Try the below:
yml = [{"id":1, "name":"Sam"},{"id":18, "name":"tom"}]
result = {}.tap do |result|
yml.each do |hash| # Iterate over array of hashes on parsed input from YAML file
hash.each do |key, value| # Iterate over each keys in the hash
result[key] ||= []
result[key] << value # Append element in the array
end
end
end
This will return the result as a hash:
{:id=>[1, 18], :name=>["Sam", "tom"]}
You can access the ids and names as
result[:id] # [1, 18]
result[:name] # ["Sam", "tom"]

How to turn a translations table into YAML translation files in ruby on rails

My app is currently using the gem i18n-active_record for translations, storing them in a table translations which has more than 5000 records.
To improve performance, I want to move the translations to YAML files, e.g. en.yml and ar.yml.
I tried File.open(Rails.root+'config/locales/en.yml', 'w') {|f| f.write translation.to_yaml } but the output generated is the following:
raw_attributes:
id: 1
locale: en
key: footer.new_car_make
value: New %{title} Cars
interpolations:
is_proc: 0
created_at: &4 2012-08-15 06:25:59.000000000 Z
updated_at: &5 2012-08-15 06:25:59.000000000 Z
Is there any easy way to make the conversion?
You can try something like this (I don't have any DB backed Rails Translations to try this)
def assign(parts, value, data)
key, *rest = parts
if rest.empty?
data[key] = value
else
data[key] ||= {}
assign(rest, value, data[key])
end
end
translations = {}
Translation.all.each do |translation|
path = [translation.locale] + translation.key.split(".")
assign(path, translation.value, translations)
end
File.write("translations.yml", translations.to_yaml)
Of course you can modify to only export the translations of a single locale or specific keys (by changing the all to a where query).
The code works as following:
It iterates all the Translations, and builds a hash with all the translations.
The key footer.new_car_make of the en translation will end up in a nested hash like:
{
"en" => {
"footer" => {
"new_car_make" => "Whatever text you have in the DB"
}
}
}
and then this hash is saved as YAML format in a file.
The assign method is called with the full key (it contains the locale as well) represented as an array (['en', 'footer', 'new_car_make']) and deconstructs into a head (first value in the array) and the rest (remaining parts).
If the remaining part is empty, it means we've reached the last element and can assign the value, otherwise we add a nested hash and recurse.
You can try this in the console (just copy paste it). As mentioned this might not run right out of the box, let me know if you have troubles.

How can I put a Postgres JSON value into a Rails fixture?

I just added a Postgres json type to a Rails/Active Record table I'm working with.
I'd like to populate a record with a default value in Rails fixtures:
fixture_id:
existing_column: "foobar"
newly_added_column: <%= JSON.dump({:reason => 'foobar'}) %>
Previously, I'd stored stringified JSON this way in a text column. However, when I now run this unit test:
test "my test" do
sut = fixtures(:fixture_id)
assert_not_nil sut.newly_added_column
end
The test fails. Because it is JSON at the database level, I don't think it's useful to dump it to a string, but the YAML fixtures can't seem to keep an object as a Hash (when I try it without the JSON.dump, I get ActiveRecord::Fixture::FormatError: a YAML error occurred parsing).
Mind you, I am using Rails 3, so I think some of the support for this may be in Rails 4, but in Rails 3, the migration to add a json Postgres column type still work.
I believe your fixture can be as simple as:
fixture_id:
existing_column: foobar
newly_added_column: {'reason':'foobar'}
To avoid having lengthy JSON structure inline in your fixtures, YAML provide a useful > operator
fixture_id:
existing_column: "foobar"
data: >
{
"can_edit": true,
"can_se": true,
"can_share": true,
"something_else": true
}
In Rails 5 it is possible to simply describe the JSON in YAML format. It will then be converted into correct JSON when fixtures are loaded in the DB:
parent:
name: A fixture
json_field:
- name: JSON Object 1
foo: bar
- name: JSON Object 2
foo: baz
(tested with JSONB attributes in Postgres)
If you are using ActiveRecord to generate content for your fixtures, you have to ensure that it converts attribute values to json. If you rely on the default serialization, it will produce hash rocket text which is invalid YAML.
For example, I have a legacy database that I am using to create fixtures for my new application (relevant code listed below):
presenter.rb
module FixturePresenter
def replacer(str)
str.downcase.tr(' ', '_').tr('-', '_')
end
# rubocop:disable Metrics/AbcSize
def plan
tag = replacer(name) + '_' + replacer(type)
<<~FIXTURE
#{tag}:
name: #{name}
type: #{type}
address: #{address}
FIXTURE
end
end
runner.rb
...
Legacy::Provider.all.each_with_index do |p, i|
p.extend(FixturePresenter)
f.puts p.plan
end
...
The address column in the plan table is a JSONB datatype. When we run this code this is the resulting YAML:
abc_plan_la_jolla:
name: ABC Plan La Jolla
type: default
address: {"street"=>"9888 Genesee Ave","unit"=>"","city"=>"La Jolla","state"=>"CA","postal_code"=>"92037"}
When you run your test you will get the very sad Syck error:
Psych::SyntaxError: (<unknown>): did not find expected ',' or '}' while parsing a flow mapping at line 4 column 12
The following change to the code will produce the correct YAML:
presenter.rb
address: #{address} to address: #{address.to_json}
The above change will produce a happy fixture definition:
abc_plan_la_jolla:
name: ABC Plan La Jolla
type: default
address: {"street":"9888 Genesee Ave","unit":"","city":"La Jolla","state":"CA","postal_code":"92037"}
Try
fixture_id:
existing_column: "foobar"
newly_added_column: "{\"reason\": \"foobar\"}"
I was maintaining a legacy Rails 4.2 application and needed to put JSON store value into a fixture. I made the following monkey patch to fix the problem. Hope this helps someone:
module ActiveRecord
class Fixture
def to_hash
h = fixture
model_class.attribute_names.each do |name|
typedef = model_class.type_for_attribute(name)
h[name] = typedef.coder.dump(h[name]) \
if typedef.is_a? ActiveRecord::Type::Serialized
end
h
end
end
end

How can I write quoted values in en.yml?

I'm writing a script that will add new translations to the en.yml file. However, when I'm dumping them back to the file, my strings are in the following format:
some_key: This is the value
I'm trying to make the output be:
some_key: "This is the value"
I'm writing the translations like this:
File.open(yaml_file, "w") do |f|
f.write(translations.to_yaml)
end
Where translations is the hash containing all the translations.
Is there any way of adding these quotes, besides manually parsing/rewriting the YAML file?
The plan (unquotes) scalar representation is the preferred version when the scalar type doesn't require escaping.
In your case, the String:
This is the value
doesn't need to be in quotes, thus, if you supply the following YAML:
key: "This is the value"
the processor may return:
key: This is the value
because they are totally equivalent. However, if you actually want to enter a quoted string as value, then you should use:
key: '"This is the value"'
or escape the double quote:
key: "\"This is the value\""
I gave a quick look at the Psych emitter code, the one invoked by the to_yaml, and there doesn't seem to be an option to force quoting on scalar.
I don't even see the option implemented in the scalar emitter code.
def visit_Psych_Nodes_Scalar o
#handler.scalar o.value, o.anchor, o.tag, o.plain, o.quoted, o.style
end
In other words, you cannot enforce quoting.
Updated for hash conversion
def value_stringify(hash)
hash.each do |k,v|
if v.kind_of? Hash
hash[k]= value_stringify(v)
else
hash[k]="\"#{v}\""
end
end
end
Now use the converted hash to store yaml.
File.open(yaml_file, "w") do |f|
f.write(value_stringify(translations).to_yaml)
end
Now it should work..
The format you get is valid YAML. However, if you really want this you could temporarily modify your data before converting it.
Normal:
{ foo: "bar" }.to_yaml
# => foo: bar
With an space after:
{ foo: "bar " }.to_yaml
# => foo: 'bar '
Note that you get single quotes and not double quotes. So if you temporarily modifying your data you could add in an placeholder which you remove later.
Example:
{ foo: "foo --REMOVE-- ", bar: "bar --REMOVE-- " }.to_yaml
.gsub(' --REMOVE-- ', '')
# => foo: 'foo'
# bar: 'bar'

Rake data import task isn't updating correctly

I have a .txt file full of attributes like so:
"12345", "1", "Kent"
"67890", "1", "New Castle"
I need this to update my County model, and so I have this rake task:
namespace :data do
desc "import data from files to database"
task :import => :environment do
file = File.open(File.join(Rails.root, "lib", "tasks", "counties.txt"), "r")
file.each do |line|
attrs = line.split(", ")
c = County.find_or_initialize_by_number(attrs[0])
c.state_id = attrs[1]
c.name = attrs[2]
c.save!
end
end
end
All seems to be well, but when I check in the console to make sure it was imported correctly, I get this:
#<County id: 2, name: nil, number: 0, state_id: 0, created_at: "2013-08-04 17:44:11", updated_at: "2013-08-04 17:44:11">
I know that it's actually importing something, because it has created exactly the right number of County records, but it's not actually updating the attributes correctly. I'm sure I'm missing something very obvious but I can't find it!
Assumption: County.number is defined as an integer field.
attrs[0] after your line.split comes out to be "\"12345\"" (a string). The find_by method defaults the lookup key to 0 (integer) given that it's looking up an integer field (number). This would explain why your code works when you manually strip out the quotation marks from the first data column in the text file.
Based on this root cause, there could be multiple ways to resolve your issue. Here's an ugly way:
c = County.find_or_initialize_by_number(Integer(attrs[0].gsub(/\"/, '')))
Ideally, I would trim out (or gsub) quotes when doing the line text split.

Resources