Rails: difference between ENV.fetch() and ENV[] - ruby-on-rails

What is the difference between these two syntax:
ENV.fetch("MY_VAR")
ENV['MY_VAR']
I've seen Rails 5 use both versions of these in difference places and can't figure out what the difference is (apart from the first one being more characters to type).

The ENV hash-like object is plain Ruby, not part of Rails. From the fine ENV#[] manual:
Retrieves the value for environment variable name as a String. Returns nil if the named variable does not exist.
and the fine ENV#fetch manual:
Retrieves the environment variable name.
If the given name does not exist and neither default nor a block a provided an IndexError is raised. If a block is given it is called with the missing name to provide a value. If a default value is given it will be returned when no block is given.
So just like Hash#[] and Hash#fetch, the only difference is that fetch allows you to specify the behavior if a key is not found (use a default value passed to fetch, default block passed to fetch, or raise an exception) whereas [] just silently gives you nil if the key isn't found.
In the specific case of:
ENV.fetch("MY_VAR")
ENV['MY_VAR']
the difference is that ENV['MY_VAR'] will give you nil if there is no MY_VAR environment variable but ENV.fetch('MY_VAR') will raise an exception.

Related

Rails NoMethodError when calling a method from a constant

I'm trying to call a method inside a class constant. It returns a NoMethodError. Here's a sample code:
class TestingA
CONSTANT_HERE = { details: get_details('example.json') }
def get_details(file)
# retrieve details here
end
end
The error that appears is:
NoMethodError (undefined method `get_details' for TestingA:Class)
Any ideas on why?
Generally, dynamic constant assignment is discouraged in Ruby. Why would you want to define a constant that can possibly change within the life-cycle of an object? We don't know exactly what get_details is doing, but what is the use case of creating an instance method that is called from a constant as well as exposing the method? We can only assume return value is dynamic at this stage. Rubocop is also going arrest you for not freezing the constants, which is bad as linters are a good tool to abide by.
Constants can be changed and there is no way to avoid this as variables in Ruby are not containers: they point towards an object. However, it is your duty to make your code readable. If you see a constant that you cannot easily discern the value of, would you think that is readable?
We should talk about how Ruby loads constants and, more generally, files. Every Ruby application has its entry point. The interpreter will need the file to load and execute the commands of the application. The Ruby interpreter will increment over each statement inside your file and execute them following a specific set of rules, until it parses the entire file. What happens when the interpreter iterates to a constant? What other types of constants are there? Hint: CONSTANT_HERE is not the only constant you are defining.
The class keyword is processed first and the interpreter creates a constant 'TestingA' and stores in that constant a class object. The name of the class instance is "TestingA", a string, named after the constant. Yes, classes and modules are constants. Each class has a constant table, which is where "TestingA" will be stored. After this, the body of the class is interpreted and a new entry is created in the constant table for "CONSTANT_HERE" and the value is set and stored. This is happening before your definition of get_details has been defined: the interpreter wants to set the value, to store it, before "get_details" has been interpreted, which is why you are experiencing the error you are.
Now we know this, and wanting to provide an example of how constants are evaluated in code, you would need to mimic the below in order to have a method defined in a constant:
def get_details(file)
"stub_return"
end
class TestingA
CONSTANT_HERE = { details: get_details('example.json') }
end
In my opinion, the above is not good practise as it is an example mis-use of constant assignment. If you want to get details from a file, define a class/method and call the API/method instead. It's neater, assigns role of responsibility and is less ambiguous.

Is there a defined exception similar to ActionController::UnpermittedParameters, for when a Parameter contains an illegal value?

I have an API endpoint with a parameter named activate_via. However, activate_via is only allowed to receive two values - "email" or "text". I want to raise an error if that parameter receives anything else.
Originally I was thinking to raise ActionController::UnpermittedParameters, however now I realize that the real purpose of that error is when a param KEY that is not permitted is passed, not a VALUE.
Is there an already defined error for this purpose? Or should I just define my own?
I'd suggest you use ArgumentError
raise ArgumentError.new("invalid argument for Activate via") unless %w(email text).include?(params['activate_via'])

rspec, validate the actual Expectation parameter

Is it possible to validate the actual parameter received by a mock objects method parameter? I cannot do a direct comparison because i'm using Fabricate and converting the object into a serialised format.
for example:
expect(user).to have_received(:add).with(valid_user())
so in that case valid_user() would accept the parameter, validate and return a boolean, to verify a valid value was passed into user.add()
can something like that be done?
So far I've been reading the documentation in https://github.com/rspec/rspec-mocks regarding Argument Matchers
Edit: in my specific case, the argument is a quite large string. I would like to validate that the string is valid by potentially running a regex against the string. So i would like to run the argument through a validation method which simply returns true/false.
You can use a custom matches in with() as shown in the spec docs https://relishapp.com/rspec/rspec-mocks/docs/setting-constraints/matching-arguments#using-a-custom-matcher

What does it mean when a variable or method or constants has preceded with an underscore in Ruby?

What does it mean in Ruby when you see methods or variables with an underscore before them like this in Rails
submit_tag _('Enroll')
:notice => _('Update card.')
or
_session_id
Are most of these just conventions, or do they imply a functional difference in the behavior of the variables/methods?
There is no difference in bahavior. It is just convertion.
But let's have a closer look:
_('A string') is actually a method not a variable. The underscore method is defined by Gettext and translates a string.
#_foo is offen used to show other developers that there is something special about the variable, and therefore it should not be used. I saw that pattern a lot for variables that are used to cache values, like:
def expensive_operation
#_expensive_operation ||= begin
# long running code...
end
end
And the underscore is sometimes used to indicate that a variable is not used in a block. Like this:
a_hash.each do |_, value|
# do something with the value, not with the key
end
Those two cases are completely different.
The second looks like it is a local variable (although it might be a method call, it's impossible to tell without the context). Local variables that begin with an underscore will not generate an "unused variable" warning if they are unused, which is why they are used to indicate a variable that is intentionally not used (as opposed to a typo or pogramming error).
The first is a call to a method named _. What this method does, depends on the class of self at that point, you will have to look at the documentation for that class.
In IRb, _ is a method that returns the result of the last expression that was evaluated.

options.fetch . I don't get it

Look at the discussion on this thread . I am not able to follow how having a block to a fetch is a better solution.
In the first patch on Rails ticket #4558:
options.fetch(:alt, File.basename(src, '.*').capitalize)
This line executes the basename and capitalize functions and then passes the result into Hash#fetch regardless of if a value for :alt already exists in the options hash.
In the updated patch:
options.fetch(:alt) { File.basename(src, '.*').capitalize }
The same basename/capitalize code is only executed when Hash#fetch needs the default value (i.e. when the :alt key does not exist in the options hash). This means the (possibly expensive) calculation of the default value can be skipped if it's not needed.
See the documentation on Hash#fetch for more details.
I don't know what duck-punching Rails has been doing to Hash#fetch, but with Plain Old Ruby Objects, I use it rather than Hash#[] because when I ask for something and it's not available, I want to know about it. This is called "Failing early" (or "Crashing early" in The Pragmatic Programmer's List of Tips)

Resources