Using YAML data in Ruby App - ruby

I want to load data from my YAML file and use the it in my ruby application. The contents available in YAML will be used in different places. Therefore, I want to read it initially and use the data whenever required.
I would like to know what is the best practice for this?
What I tried is
config.yaml
db:
username: admin
password: admin
config.rb
class Config
class << self
attr_accessor :uname, :pwd
def load
config_data = YAML.load_file("c:\config.yml")
#uname = config_data['db']['username']
#pwd = config_data['db']['password']
end
end
end
my_app.rb
Config.load
puts Config.uname
puts Config.pwd
Please let me know whether this is a right way to load and use YAML data. If not please share the best practice.

It hardly depends on your intentions, but generally speaking, yes, it is mostly correct approach. What do you want to do more, is to cache once read data:
class Config
class << self
attr_accessor :uname, :pwd
def load force_reload = false
return #config_data if #config_data && !force_reload
#config_data = YAML.load_file("c:\config.yml")
#uname = #config_data['db']['username']
#pwd = #config_data['db']['password']
end
end
end
Here we return immediately, if the #config was already loaded, allowing fast subsequent calls to Config.config.
Whether one wants to explicitly reload the config, it should pass the parameter to load:
Config.load true # true means reload YAML explicitly

Related

YAML Ruby Load multiple environment variables

I inherited a tool that is working correctly but when I try to extend it it just fails. Since I am new to ruby and yaml I dont really know what is the reason why this fails...
So I have a class config that looks like this
class Configuration
def self.[] key
##config[key]
end
def self.load name
##config = nil
io = File.open( File.dirname(__FILE__) + "/../../../config/config.yml" )
YAML::load_documents(io) { |doc| ##config = doc[name] }
raise "Could not locate a configuration named \"#{name}\"" unless ##config
end
def self.[]=key, value
##config[key] = value
end
end
end
raise "Please set the A environment variable" unless ENV['A']
Helpers::Configuration.load(ENV['A'])
raise "Please set the D environment variable" unless ENV['D']
Helpers::Configuration.load(ENV['D'])
raise "Please set the P environment variable" unless ENV['P']
Helpers::Configuration.load(ENV['P'])
So I had a first version with the environment variable A that worked fine, then when I want to integrate 2 more environment variables it fails (they are different key/value sets). I did debug it and it looks like when it reads the second key/value it removes the other ones (such as reading the 3rd removes the previous 2, so I end up with ##config with only the 3rd key/value par instead of all the values I need).
It is probably easy to fix this, any idea how?
Thanks!
EDIT:
The config file use to look like:
Test:
position_x: “56”
position_y: “56”
Now I want to make it like
“x56”:
position_x: “56”
“x15”:
position_x: “15”
“y56”:
position_y: “56”
“y15”:
position_y: “15”
My idea is that I set them separately and I don’t need to create all the combinations…
Each time you call load you delete the previous configuration (in the line ##config = nil). If you want the configuration to be a merger of all files you will want to merge the new configuration to the existing configuration rather than overriding it.
Something like this:
def self.load name
##config ||= {}
io = File.open( File.dirname(__FILE__) + "/../../../config/config.yml" )
YAML::load_documents(io) do |doc|
raise "Could not locate a configuration named \"#{name}\"" unless doc[name]
##config.merge!(doc[name])
end
end
Be aware that if the code was written as it has been because the method was called more than once, and the configuration is expected to reset between reads, you will need to explicitly reset the configuration now:
class Configuration
# ...
def reset_configuration
#config = {}
end
end
Helpers::Configuration.reset_configuration
raise "Please set the A environment variable" unless ENV['A']
Helpers::Configuration.load(ENV['A'])
raise "Please set the D environment variable" unless ENV['D']
Helpers::Configuration.load(ENV['D'])
raise "Please set the P environment variable" unless ENV['P']
Helpers::Configuration.load(ENV['P'])
I'd access the YAML using:
YAML::load_file(File.expand_path("../../../config/config.yml", File.dirname(__FILE__)))
expand_path cleans up the '..' chain and returns the cleaned-up version, relative to FILE. For instance:
foo = '/path/to/a/file'
File.expand_path("../config.yml", File.dirname(foo)) # => "/path/to/config.yml"
load_file reads and parses the entire file and returns it.

read json in Ruby and set variables for use in another class

The need here is to read a json file and to make the variables which is done from one class and use them with in another class. What I have so far is
helper.rb
class MAGEINSTALLER_Helper
#note nonrelated items removed
require 'fileutils'
#REFACTOR THIS LATER
def load_settings()
require 'json'
file = File.open("scripts/installer_settings.json", "rb")
contents = file.read
file.close
#note this should be changed for a better content check.. ie:valid json
#so it's a hack for now
if contents.length > 5
begin
parsed = JSON.parse(contents)
rescue SystemCallError
puts "must redo the settings file"
else
puts parsed['bs_mode']
parsed.each do |key, value|
puts "#{key}=>#{value}"
instance_variable_set("#" + key, value) #better way?
end
end
else
puts "must redo the settings file"
end
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
Which is called in a file of Pre_start.rb
class Pre_start
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
mi_h = MAGEINSTALLER_Helper.new
mi_h.load_settings()
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
file="www/depo/newfile-#{bs_MAGEversion}.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{bs_MAGEversion}/file-#{bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
the josn file is valid json and is a simple object (note there is more just showing the relevant)
{
"bs_mode":"lite",
"bs_MAGEversion":"1.8.0.0"
}
The reason I need to have a json settings file is that I will need to pull settings from a bash script and later a php script. This file is the common thread that is used to pass settings each share and need to match.
Right now I end up with an empty string for the value.
The instance_variable_setis creating the variable inside MAGEINSTALLER_Helper class. That's the reason why you can't access these variables.
You can refactor it into a module, like this:
require 'fileutils'
require 'json'
module MAGEINSTALLER_Helper
#note nonrelated items removed
#REFACTOR THIS LATER
def load_settings()
content = begin
JSON.load_file('scripts/installer_settings.json')
rescue
puts 'must redo the settings file'
{} # return an empty Hash object
end
parsed.each {|key, value| instance_variable_set("##{key}", value)}
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
class PreStart
include MAGEINSTALLER_Helper
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
load_settings # The method is available inside the class
file="www/depo/newfile-#{#bs_MAGEversion}.tar.gz"
if !File.exist?(file)
download("http://www.dom.com/#{#bs_MAGEversion}/file-#{#bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
I refactored a little bit to more Rubish style.
On this line:
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
instance_variable_get isn't retrieving from the mi_h Object, which is where your value is stored. The way you've used it, that line is equivalent to:
bs_MAGEversion=#bs_MAGEversion
Changing it to mi_h.instance_variable_get would work. It would also be painfully ugly ruby. But I sense that's not quite what you're after. If I read you correctly, you want this line:
mi_h.load_settings()
to populate #bs_MAGEversion and #bs_mode in your Pre_start object. Ruby doesn't quite work that way. The closest thing to what you're looking for here would probably be a mixin, as described here:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
We do something similar to this all the time in code at work. The problem, and solution, is proper use of variables and scoping in the main level of your code. We use YAML, you're using JSON, but the idea is the same.
Typically we define a constant, like CONFIG, which we load the YAML into, in our main code, and which is then available in all the code we require. For you, using JSON instead:
require 'json'
require_relative 'helper'
CONFIG = JSON.load_file('path/to/json')
At this point CONFIG would be available to the top-level code and in "helper.rb" code.
As an alternate way of doing it, just load your JSON in either file. The load-time is negligible and it'll still be the same data.
Since the JSON data should be static for the run-time of the program, it's OK to use it in a CONSTANT. Storing it in an instance variable only makes sense if the data would vary from instance to instance of the code, which makes no sense when you're loading data from a JSON or YAML-type file.
Also, notice that I'm using a method from the JSON class. Don't go through the rigamarole you're using to try to copy the JSON into the instance variable.
Stripping your code down as an example:
require 'fileutils'
require 'json'
CONTENTS = JSON.load_file('scripts/installer_settings.json')
class MAGEINSTALLER_Helper
def download(from,to)
puts "completed download for #{from}\n"
end
end
class Pre_start
def initialize(params=nil)
file = "www/depo/newfile-#{ CONFIG['bs_MAGEversion'] }.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{ CONFIG['bs_MAGEversion'] }/file-#{ CONFIG['bs_MAGEversion'] }.tar.gz", file)
else
puts "mage package exists"
end
end
end
CONFIG can be initialized/loaded in either file, just do it from the top-level before you need to access the contents.
Remember, Ruby starts executing it at the top of the first file and reads downward. Code that is outside of def, class and module blocks gets executed as it's encountered, so the CONFIG initialization will happen as soon as Ruby sees that code. If that happens before you start calling your methods and creating instances of classes then your code will be happy.

Two versions of each blog post in Jekyll

I need two versions of each of my posts in a very simple Jekyll setup: The public facing version and a barebones version with branding specifically for embedding.
I have one layout for each type:
post.html
post_embed.html
I could accomplish this just fine by making duplicates of each post file with different layouts in the front matter, but that's obviously a terrible way to do it. There must be a simpler solution, either at the level of the command line or in the front matter?
Update:
This SO question covers creating JSON files for each post. I really just need a generator to loop through each post, alter one value in the YAML front matter (embed_page=True) and feed it back to the same template. So each post is rendered twice, once with embed_page true and one with it false. Still don't have a full grasp of generators.
Here's my Jekyll plugin to accomplish this. It's probably absurdly inefficient, but I've been writing in Ruby for all of two days.
module Jekyll
# override write and destination functions to taking optional argument for pagename
class Post
def destination(dest, pagename)
# The url needs to be unescaped in order to preserve the correct filename
path = File.join(dest, CGI.unescape(self.url))
path = File.join(path, pagename) if template[/\.html$/].nil?
path
end
def write(dest, pagename="index.html")
path = destination(dest, pagename)
puts path
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write(self.output)
end
end
end
# the cleanup function was erasing our work
class Site
def cleanup
end
end
class EmbedPostGenerator < Generator
safe true
priority :low
def generate(site)
site.posts.each do |post|
if post.data["embeddable"]
post.data["is_embed"] = true
post.render(site.layouts, site.site_payload)
post.write(site.dest, "embed.html")
post.data["is_embed"] = false
end
end
end
end
end

Transactions in Ruby Sequel module: how to get DB object?

I'm working in a Sinatra application using Sequel.
I want to make a transaction, according to the manual I have to use the DB object, how can I get this object from any part of my code?
You can define it in your base app.rb (or equivalent) or include a separate file where you configure the DB object if you wish.
For example, in one of my Sinatra apps, I have an app.rb that includes a
class App < Sinatra::Application
#lots of stuff here...
end
require_relative 'models/init'
In my models/init.rb I configure DB
require 'sequel'
conf = YAML.load(File.open(File.expand_path('./config/dbconn.yml')))
env = ENV['RACK_ENV'] || 'development'
DB = Sequel.connect(host:conf['database'][env]['host'],
port:conf['database'][env]['port'],
database:conf['database'][env]['schema'],
username:conf['database'][env]['username'],
password:conf['database'][env]['password'],
adapter:conf['database'][env]['adapter'],
encoding:conf['database'][env]['encoding'])
raise "Unable to connect to #{conf['database'][env]['host']}" unless DB.test_connection
...
That's one way. Hope it helps.
You mention that you want to reference from any part of your code; however I've found that encapsulated within the models is where I tend to wrap transactions; and from there it's relatively easy:
class X < Sequel::Model
def self.y
self.db.transaction {
...
end
end
def z
db.transaction {
...
}
end
end

ruby: how to load .rb file in the local context

How this simple task can be done in Ruby?
I have some simple config file
=== config.rb
config = { 'var' => 'val' }
I want to load config file from some method, defined in main.rb file so that the local variables from config.rb became local vars of that method.
Something like this:
=== main.rb
Class App
def loader
load('config.rb') # or smth like that
p config['var'] # => "val"
end
end
I know that i can use global vars in config.rb and then undefine them when done, but i hope there's a ruby way )
The config file.
{ 'var' => 'val' }
Loading the config file
class App
def loader
config = eval(File.open(File.expand_path('~/config.rb')).read)
p config['var']
end
end
As others said, for configuration it's better to use YAML or JSON. To eval a file
binding.eval(File.open(File.expand_path('~/config.rb')).read, "config.rb")
binding.eval(File.read(File.expand_path('~/config.rb')), "config.rb")
This syntax would allow you to see filename in backtraces which is important. See api docs [1].
Updated eval command to avoid FD (file descriptor) leaks. I must have been sleeping or maybe should have been sleeping at that time of the night instead of writing on stackoverflow..
[1] http://www.ruby-doc.org/core-1.9.3/Binding.html
You certainly could hack out a solution using eval and File.read, but the fact this is hard should give you a signal that this is not a ruby-like way to solve the problem you have. Two alternative designs would be using yaml for your config api, or defining a simple dsl.
The YAML case is the easiest, you'd simply have something like this in main.rb:
Class App
def loader
config = YAML.load('config.yml')
p config['var'] # => "val"
end
end
and your config file would look like:
---
var: val
I do NOT recommend doing this except in a controlled environment.
Save a module to a file with a predetermined name that defines an initialize and run_it methods. For this example I used test.rb as the filename:
module Test
##classvar = 'Hello'
def initialize
#who = 'me'
end
def get_who
#who
end
def run_it
print "#{##classvar} #{get_who()}"
end
end
Then write a simple app to load and execute it:
require 'test'
class Foo
include Test
end
END {
Foo.new.run_it
}
# >> Hello me
Just because you can do something doesn't mean you should. I cannot think of a reason I'd do it in production and only show it here as a curiosity and proof-of-concept. Making this available to unknown people would be a good way to get your machine hacked because the code could do anything the owning account could do.
I just had to do a similar thing as I wanted to be able to load a "Ruby DLL" where it returns an anonymous class ( a factory for instances of things ) I created this which keeps track of items already loaded and allows the loaded file to return a value which can be anything - a totally anonymous Class, Module, data etc. It could be a module which you could then "include" in an object after it is loaded and it could could supply a host of "attributes" or methods. you could also add an "unload" item to clear it from the loaded hash and dereference any object it loaded.
module LoadableModule
##loadedByFile_ = {};
def self.load(fileName)
fileName = File.expand_path(fileName);
mod = ##loadedByFile_[fileName];
return mod if mod;
begin
Thread.current[:loadReturn] = nil;
Kernel.load(fileName);
mod = Thread.current[:loadReturn];
##loadedByFile_[fileName] = mod if(mod);
rescue => e
puts(e);
puts(e.backtrace);
mod = nil;
end
Thread.current[:loadReturn] = nil;
mod
end
def self.onLoaded(retVal)
Thread.current[:loadReturn] = retVal;
end
end
inside the loaded file:
LoadableModule.onLoaded("a value to return from the loaded file");

Resources