Boolean logic with Rails ENV variables - ruby-on-rails

Since Rails ENV variables should only have string values it could be the problem to decide how to use ENV variable for use cases which need boolean logic. For example since ENV variable have a string value and it would always be truthy it wouldn't be too nice to do something like that:
if ENV['MY_VARIABLE']
# do something
else
# do something else
end
So there are at least 2 ways to accomplish things like above:
Initialize variable with particular value and check it
if ENV['MY_VARIABLE'] == 'some string'
# do something
elsif ENV['MY_VARIABLE'] == 'some other string'
# do something else
end
Or just initialize variable with any value and check if it was initialized (the code could be exactly as we wanted).
if ENV['MY_VARIABLE']
# do something
else
# do something else
end
The question is what option is more preferred and what pros and cons each of them could have?

If you use Rails 5+, you can do ActiveModel::Type::Boolean.new.cast(ENV['MY_VARIABLE']).
In Rails 4.2, use ActiveRecord::Type::Boolean.new.type_cast_from_user(ENV['MY_VARIABLE']).
Documentation Rails 5+: https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html

Environment Variables as the name suggests, are environment dependent variables which store different values for same keys based on the environment(production, staging, development) you working on.
e.g. it holds an Access_Key for some api which has sandbox mode and production mode. Thus, to make your code DRY and effective you set an environment variable to get that access_key of sandbox mode for development/staging and live key for production.
What you are trying to do is use them unlike the reason they are defined for, no doubt they can be used that way. Since they are constants what I recommend is doing the following.
create a constants.rb file in your initializers containing
class Constant
BOOL_CONSTANT = ENV['MY_VARIABLE'].present?
# OR
BOOL_CONSTANT = ENV['MY_VARIABLE'] == 'true'
end
then you can use it anywhere you like. This way you can achieve what you want to but under the hood. ;)

You should probably refactor your code and use a custom class, so it's more maintenable and changes may be done easily:
class MyEnv
TRUTHY_VALUES = %w(t true yes y 1).freeze
FALSEY_VALUES = %w(f false n no 0).freeze
attr_reader :value
def initialize(name)
#value = ENV[name].to_s.downcase
end
def to_boolean
return true if TRUTHY_VALUES.include?(value.to_s)
return false if FALSEY_VALUES.include?(value.to_s)
# You can even raise an exception if there's an invalid value
raise "Invalid value '#{value}' for boolean casting"
end
end
# Usage example:
MyEnv.new("MY_VARIABLE").to_boolean
I think that, for boolean environment variables, it's more human-friendly to have values like yes, true, no... instead of present or not present variables.
The only drawback that I can see here is performance, where you jump from nil checking (easy) to a comparisson of strings (a little bit more complex). Give the computer power these days, and if performance is not an issue for you, it would be no problem for you.
So, in conclussion: strings-checks are more human friendly and slower, presence-checks are faster but more obscure.

The answers here are fine, but I wanted the most conciseness without sacrificing performance or complexity. Here is a modified version of #Wikiti's answer. I don't think it's necessary to define falsey values. Anything contained in the set of true values can be effectively considered as 'true', otherwise treat it as false.
Note that this is intentionally different than the ActiveModel Boolean class, which will treat everything as true if it is NOT contained in a falsey value. If you're looking for strict boolean flags, IMO it's a better practice to have false as the default value, and true to only be returned if the value is explicitly defined as such. Programming languages tend to use false as the default in a boolean variable, so this better practice is grounded in intuitiveness.
class EnvTrue
TRUE_VALUES = %w[1 y Y true TRUE True].freeze
def self.[](key)
TRUE_VALUES.include?(v)
end
end
# Usage example:
EnvTrue['MY_VARIABLE'] # returns true or false
ENV['MY_VARIABLE'] # When you want the actual value

Related

ActiveRecord attribute_present? lies. How can I fix it?

The docs for attribute_present? say:
Returns true if the specified attribute has been set by the user or by
a database load ...
BUT... that's not true! We see here that Rails initializes attributes on a new object from database defaults.
So, suppose we have a users table with age not null default 0. Then
User.new.attribute_present?(:age) == true
But it hasn't been set by us OR a database load.
Perhaps I'm arguing semantics, but in any case, I'd like a method that does what it says: tells me if a field has been explicitly set.
e.g.
u = User.new
# u.attribute_set?(:age) == false
u.age = u.age #set explicitly to default, for example
# u.attribute_set?(:age) == true
Does that exist?
As far as I know, there isn't a way (provided you have default arguments in your db) (as is good design)
Edited: edited from original answer based on Z5h's comments below

Constants in MongoDB using Moingoid

Are there any good common practices for storing project-wide constants in Mongodb via Mongoid? Is it even worth keeping them in the database?
Constants are, well, constants. Pi is a constant. You don't need a database for it, you know its value. But probably you meant something more like "A value that's pretty static and is unlikely to change often". If this is the case, then it indeed makes some sense to store those values in a DB. There are probably gems for this kind of stuff, but in vanilla Mongoid I'd do something like this:
class Setting
field :_id, type: String
field :value
def self.read_value name
# return value or nil
Setting.where(_id: name).first.try(:value)
end
def self.write_value name, value
where(_id: name).upsert(value: value)
end
end
# usage
num_workers = Setting.read_value 'number_of_workers'
Setting.write_value "upload_dir", '/var/www/uploads'

Rails static data set

What is the best way to handle a static data set (non-dynamic)?
For instance, let's say you have a model that has a set of 10 different instances, each of which is unique, but none of which will ever change throughout the lifetime of your application. It seems overkill to create an activerecord model and store this data in the database, but it seems ugly to create a generic class and store this data in the code.
What is accepted as a best practice?
Example:
You have a Rate and a User. A User can have a level from 1-10, when the level changes, the rate changes. The rate might have other information, so simply storing it as an attribute on the User might be more trouble than it's worth. Would it make sense to tie it to a Rate or to create it as a method on the User like this:
def rate
case self.level
when 1:
{ value: "foo", something: "bar", else: "baz" }
when 2:
# etc
end
end
It seems that neither of the solutions are ideal, but I'm not sure if there is something else ideal that could happen.
I would store this information in a YAML file. You could use the RailsConfig gem and create a YAML file like
level:
1:
some: value
another: value
2:
some: second value
another: second value
And then access it with
rate = 2
val = Settings.level[rate.to_s].some
(I'm not completely sure with numbers as keys in YAML, maybe you have to escape them)
I use constants in this cases: constants do not change after the declaration, but the declaration can be dynamic:
OS =
case RUBY_PLATFORM
when /linux/ then :linux
when /osx/ then :osx
when /windows/ then :windows
else :unknown
Performance should be better when using constants for static values, because they should be memoized (and because staticity should be their purpose, so probably Ruby implementations trust about it; I read something about JRuby and constants implementation, I'll post it if I'll find. EDIT I found it: http://blog.headius.com/2012/09/avoiding-hash-lookups-in-ruby.html).

Rails: Convert string to variable (to store a value)

I have a parameter hash that contains different variable and name pairs such as:
param_hash = {"system_used"=>"metric", "person_height_feet"=>"5"}
I also have an object CalculationValidator that is not an ActiveRecord but a ActiveModel::Validations. The Object validates different types of input from forms. Thus it does not have a specific set of variables.
I want to create an Object to validate it like this:
validator = CalculationValidator.new()
validator.system_used = "metric"
validator.person_height_feet = 5
validator.valid?
my problem right now is that I really would not prefer to code each CalculationValidator manually but rather use the information in the Hash. The information is all there so what I would like to do is something like this, where MAKE_INTO_VARIABLE() is the functionality I am looking for.
validator = CalculationValidator.new()
param_hash.each do |param_pair|
["validator.", param_pair[0]].join.MAKE_INTO_VARIABLE() = param_pair[1]
# thus creating
# "validator.system_used".MAKE_INTO_VARIABLE() = "metric"
# while wanting: validator.system_used = "metric"
# ...and in the next loop
# "validator.person_height_feet".MAKE_INTO_VARIABLE() = 5
# while wanting: validator.person_height_feet = 5
end
validator.valid?
Problem:
Basically my problem is, how do I make the string "validator.person_height" into the variable validator.person_height that I can use to store the number 5?
Additionally, it is very important that the values of param_pair[1] are stored as their real formats (integer, string etc) since they will be validated.
I have tried .send() and instance_variable_set but I am not sure if they will do the trick.
Something like this might work for you:
param_hash.each do |param, val|
validator.instance_eval("def #{param}; ##{param} end")
validator.instance_variable_set("##{param}", val)
end
However, you might notice there's no casting or anything here. You'd need to communicate what type of value each is somehow, as it can't be assumed that "5" is supposed to be an integer, for example.
And of course I probably don't have to mention, eval'ing input that comes in from a form isn't exactly the safest thing in the world, so you'd have to think about how you want to handle this.
Have you looked at eval. As long as you can trust the inputs it should be ok to use.

Making dynamic scope helpers in Rails

Several of my partials can be rendered in two "modes". If full_display is false, I don't render several fields. To make things easy I wanted to make one of the "modes" default - if full_display is not defined, treat it as false. I came up with this code:
(((not defined?(full_display).nil?) && full_display) || false)
Quite a lot to move around. It would be nice to put it inside a helper or something, but since Ruby has only lexical scope I can't think of any good way to do it.
Bad ideas that I've already tried:
on the top of the partial do <% display = long code that is above %> and use display through the code, but creating local variables in a view looks bad and has to be copied into every partial using full_display.
wrap it inside a string, put it into a helper and use eval(display_helper) in view, but obviously this creates security concerns.
That's quite a convoluted way of saying something as simple as:
defined?(full_display) && full_display
In Ruby there are two values that are non-true, nil and false, all others evaluate as true, which includes 0, empty string, among others, that would otherwise evaluate as false in Perl, PHP, and C. Testing with .nil? is usually reserved for those rare cases where you want to differentiate between false and undefined, and this is sometimes the case with boolean fields where a missing value is different from a false value.
In any case, in the view space it is a lot easier to assign defaults using the or-equals operator ||= like this:
<% full_display ||= false %>
That is equivalent to full_display = full_display || false. This does two things. First, it defines the full_display variable even if it was not previously created, and secondly it ensures that the it will contain a value that is at least false, never undefined.
You will see the ||= default pattern a lot in Ruby code as it's an easy way to assign something in the case where it will be nil otherwise.

Resources