Rails load YAML to hash and reference by symbol - ruby-on-rails

I am loading a YAML file in Rails 3.0.9 like this:
APP_CONFIG = YAML.load(File.read(File.expand_path('../app.yml', __FILE__)))
It loads the all of the contents like hierarchical hashes, no problem. The part I don't like is the fact that the hashes can only be accessed with single or double quotes but not a symbol.
APP_CONFIG['mailer']['username'] # works fine
APP_CONFIG[:mailer][:username] # doesn't
Any thoughts?

Try using the HashWithIndifferentAccess like
APP_CONFIG = HashWithIndifferentAccess.new(YAML.load(File.read(File.expand_path('../app.yml', __FILE__))))

An alternative solution is to have the keys which you wish to access as a symbol prepended with a colon. For example:
default: &default
:symbol: "Accessed via a symbol only"
string: "Accessed via a string only"
development:
<<: *default
test:
<<: *default
production:
<<: *default
Later you can then access these like so:
APP_CONFIG[:symbol]
APP_CONFIG['string']
Note that I am using YAML::ENGINE.yamler = "syck". Not sure if this works with psych. (Psych definitely won't support key merging as I showed in the example though.)
About using HashWithIndifferentAccess: using it has the side effect of creating duplicate keys: one for symbol access and one for string access. This might be nefarious if you pass around YAML data as arrays. Be aware of this if you go with that solution.

If you are working in Ruby on Rails, You might want to take a look at symbolize_keys(), which does exactly what the OP asked for. If the hash is deep,you can use deep_symbolize_keys(). Using this approach, the answer is
APP_CONFIG = YAML.load(File.read(File.expand_path('../app.yml', __FILE__))).deep_symbolize_keys

Psych (a.k.a. YAML) provides the keyword argument :symbolize_names to load keys as symbols. See method reference
file_path = File.expand_path('../app.yml', __FILE__)
yaml_contents = File.read(file_path)
APP_CONFIG = YAML.safe_load(yaml_contents, symbolize_names: true)

This is the same from the selected answer, but with a better syntax:
YAML.load(File.read(file_path)).with_indifferent_access

There is another potential answer I discovered while digging around.
You can forgo HashWithIndifferentAccess.new by instead adding this to the top of your YAML files:
--- !map:HashWithIndifferentAccess
then simply YAML.load like normal. The only trick is that rails needs to already be loaded if you are doing this in your environment for use in initializers, etc. (like I am).

Rails has a special method to symbolize keys.
You can use load_file method and get rid of File.read
Not sure if you need expand_path also, the default directory is rails root.
I'd write it that simple:
YAML::load_file('app.yml').symbolize_keys

Just use appropriate option in your YAML parser. For instance, symbolize_names in Psych:
APP_CONFIG = YAML.load(File.read(File.expand_path('../app.yml', __FILE__)), symbolize_names: true)
See RDoc: https://ruby-doc.org/stdlib-2.6.1/libdoc/psych/rdoc/Psych.html#method-c-load.

If you're using pure Ruby (i.e. no Rails), you could intermediately change to JSON format. The JSON lib's parse method can symbolize keys.
http://ruby-doc.org/stdlib-2.0.0/libdoc/json/rdoc/JSON.html#method-i-parse
Here's what I mean:
JSON.parse(JSON.dump(YAML.load_file(File.expand_path('../app.yml', __FILE__))), symbolize_names: true)
Note: This adds overhead of conversion to and from json.

You are probably used to the params hash in Rails, which is actually a HashWithIndifferentAccess rather than a standard ruby Hash object. This allows you to use either strings like 'action' or symbols like :action to access the contents.
With a HashWithIndifferentAccess, you will get the same results regardless of what you use, but keep in mind this only works on HashWithIndifferentAccess objects.
So to make this work with YAML, you'll have to load the result of YAML.load into a HashWithIndifferentAccess, like so:
APP_CONFIG = HashWithIndifferentAccess.new( YAML.load(File.read(File.expand_path('../app.yml', __FILE__))) )

I usually don't use HashWithIndifferentAccess just to avoid confusion and prevent inconsistencies in the way that it is accessed, so what I would do instead is tack on a .deep_symbolize_keys to get the whole thing in symbol key form.

Related

How to use database.yml define json file for each environment

I working on json file format in rails.
I like to use environment in config/databse.yml (or better place) to define what json file to use in the app.
But have no idea, unable to find any sample.
Any help please.
Existing code in helper as follow
def get_table(foo)
get_data = if [:production, :sandbox].include?(Rails.env)
File.read("support/product.json")
else
File.read("support/sample.json")
end
JSON.parse(get_data)
end
If I understand you correctly then you are trying to load a JSON file based on the current environment? I'm not sure a JSON file is really the kind of data store you are looking for... but that is a different question.
In your case, I would use an environment variable to set the file name that is to be used during the execution of the application.
Change the helper to something like:
def get_table
database_content_from_file = File.read(ENV['database_file'])
JSON.parse(get_data)
end
You can now set the environment variable 'database_file' in each of your different types of environments. This can be done by setting a system-wide variable or using a gem like https://github.com/laserlemon/figaro (which I highly encourage you to use).
With this, you could set ENV['database_file'] to 'support/sample.json' in your development environment and set it to 'support/product.json' in production.
I hope this answers your question. If not please rephrase your question in a manner that is easier to understand.

Rails 4 - Yaml configuration file

I have this file config/application.yml
settings:
info:
name: MyAppName
domain: example.com
contact:
email: mail#example.com
phone: 1234567890
And in the environment.rb i have this
AppConfig = YAML::load_file('config/application.yml')
So now i can access this by using AppConfig["settings"]["info"]["name"]
How can i access this by using AppConfig.settings.info.name ?
If you are using Rails 4.2 or higher, you can use config_for for the config files. They need to be placed under /config folder. (haven't tried otherwise)
In your case it would be: config = Rails.application.config_for(:application)
This is more clear and Rails way to load configs into application.
Then you can use OpenStruct to have dot notation enabled for it.
APP_CONFIG = OpenStruct.new(config)
Here's an easy way to do this without any gems, though I'm not sure about the performance if you are doing this frequently.
The idea is to first convert the data to JSON, and then parse the JSON to OpenStruct (which is built into Ruby):
json_data = YAML::load_file('config/application.yml').to_json
data = JSON.parse(json_data, object_class: OpenStruct)
This will deeply convert all hashes to OpenStructs and also correctly handles arrays.
As an example, if I have this YAML:
people:
-
name: 'Jerry Rasmussen'
address:
address_1: '123 Street St'
-
name: 'Sara DeWetzel'
Then it can be loaded and used like so:
json_data = YAML::load_file('config/people.yml').to_json
data = JSON.parse(json_data, object_class: OpenStruct)
data.people.first.name
=> Jerry Rasmussen
data.people.first.address.address_1
=> 123 Street St
There is no built-in way to convert a Hash into a construct that's accessible via dot syntax. You can either use a gem like settingslogic gem and point it to your application.yml file OR take a look at the source to find out the process by which this is done. I think the easiest and most robust approach is to use a popular (read: well tested in the wild) and well-documented gem vs. rolling your own.
# app/models/settings.rb
class Settings < Settingslogic
source "#{Rails.root}/config/application.yml"
namespace Rails.env
end
You can then access the individual settings via
Settings.info.name
# MyAppName
You can install hash dot gem
And, then use following code.
require 'hash_dot'
AppConfig = YAML::load_file('config/application.yml')
and, now call
AppConfig.settings.info.name
It will return your desired out put.
You can use recursive-open-struct gem
app_config = YAML.load_file('config/application.yml').with_indifferent_access
ros = RecursiveOpenStruct.new(app_config)
puts ros.settings.info.name # MyAppName
You could look into rails native
ActiveSupport::OrderedOptions < Hash
You still would have to convert your hashes with some function though, but as a starting point, if you really don't want any gem.

YAML configuration file: Why 'example' instead of :example?

I set up an environment YAML file for environment specific variables like username and password. To use these variables in my app, I need to use APP_CONFIG['username'] instead of APP_CONFIG[:username]. Why is this? How would I enable the latter instead? Not a major issue, but it bothers me to not know the cause of the difference.
config/initializers/load_app_config.rb
APP_CONFIG = YAML.load_file("#{Rails.root}/config/app_config.yml")[Rails.env]
config/app_config.yml
development:
username: development_name
password: secret
production:
username: production_name
password: super_secret
By default, a YAML key is rendered as String.
development:
username: development_name
password: secret
is accessible by
APP_CONFIG = YAML.load_file("#{Rails.root}/config/app_config.yml")[Rails.env]
APP_CONFIG['development']['username']
# => "development_name"
I you want a specific key to be a symbol, you should prefix it with : in the YAML file.
development:
:username: development_name
:password: secret
APP_CONFIG = YAML.load_file("#{Rails.root}/config/app_config.yml")[Rails.env]
APP_CONFIG['development'][:username]
# => "development_name"
APP_CONFIG['development']['username']
# => nil
Normally, this is not done because this is a specific Ruby behavior. Other languages might not be happy about the leading :.
If you specifically wants to access keys as symbol, you can either use symbolize_keys!
APP_CONFIG = YAML.load_file("#{Rails.root}/config/app_config.yml")[Rails.env].simbolize_keys!
but most of the time, the effort is not worth. Internally, the 90% of libraries converts Symbols to Strings during a comparison, especially when you deal with hashes with indifferent access. So, at the end of the story, you might want to keep strings in this case.
Last option would be to create a HashWithIndifferentAccess
APP_CONFIG = HashWithIndifferentAccess.new(YAML.load_file("#{Rails.root}/config/app_config.yml")[Rails.env])
This will allow you to access
APP_CONFIG[:development][:username]
APP_CONFIG['development'][:username]
APP_CONFIG['development']['username']
indifferently. It works by storing the hash keys to string internally and converting the request to [] to string, so that it always works. This is the class used by several Rails components, including the famous params[] hash in the controllers.
Use symbolize_keys on the hash returned by YAML.load_file

application wide global variable

In Rails, where should I define the variable which can be recognized by every layer of Rails stacks.
For example, I would like to have a CUSTOMER_NAME='John' variable which can be accessed in helper, rake task, controller and model. Where should I define this variable in Rails app?
I am using Rails v2.3.2
In an initializer in /app/config/initializers all .rb files in here get loaded, I usually create one called preferences.rb for things like this.
See: http://guides.rubyonrails.org/configuring.html#using-initializer-files
An alternative approach is to set a key on the config object in config/application.rb, like so:
MyApp::Application.configure do
# ...
config.my_key = 'some "global" value'
end
You can then access my_key from anywhere in your app with just this:
MyApp::Application.config.my_key
Also, Mike Perham has described a similar, though a more comprehensive approach in his blog post.
You want a true global constant? Use ::COSTUMER_NAME.
You want a true global variable? Use $COSTUMER_NAME (discouraged).
You want a request-global variable? Use the Hash in the #env method.

Is there any gem can dump the data from and to yml files?

I'm find such a gem a whole day, but not find a good one. I want to write one, but I'm not able to do it.
The data in my database may be English text, which will be dump to a yml file with plain text. And some are non-English text, which will be binary type.
And both of them may have such code:
<% xxx %>
When I use rake db:fixtures:load to load them into database, error may occur: method xxx not found.
I wan't to find a good gem can handle this problem. Thank for any help
UPDATE
I have gave up finding such a gem. At first, I think it's a easy task, but now, after some researchings, I have to say, it's much harder than I expected.
The reason you are having problems is because the Fixture loader will pass your fixture through erb before loading the data. This means that if you have <% xxx %> in your yaml file then Rails will see that as erb and try to run a method called xxx.
There does not seem to be an easy way to turn off erb processing of fixtures. I have tried replacing the fixtures with CSV fixtures and this still does ERB pre-processing.
Without a simple answer I have to ask the question Why do you have these strings in your file?
Do you want them to be expanded by erb?
Err...I'm not sure if you actually need a gem for this? Rails natively can turn any model into YAML.
Let's say you have a model called "Objects". You could hit a route that looks like:
/objects.yaml
and you would get a giant text file of all your Objects in YAML form.
Of course, you would want to have something like:
respond_to do |format|
format.yaml {render :yaml => #objects}
end
in your restful controller.
If you'd rather not hit a route to do this, you can always do
#yaml = []
#objects.each do |object|
#yaml.push object.to_yaml
end
anywhere in ruby, which will give you an array of yaml objects, that you can then write to a file at your leisure.
I imagine that if rails itself is generating the yaml, then it would be able to then later load it as a fixture?

Resources