Mock Rails 4 application config custom value - ruby-on-rails

In Rails::Application, I add a custom configuration using...
config.x.cache_config = config_for(:cache)
In my tests, I want to see how code using this behaves depending on how the configuration is defined in cache.yml. To set the various conditions in my rspec tests, I want to do something like...
allow(Rails.application.config.x).to recieve(:cache_config).and_return({})
But this doesn't work. It gets an error stating
#<Rails::Application::Configuration::Custom ... > does not implement: cache_config

After much digging and testing in pry, I figured this out.
Short answer:
allow(Rails.application.config.x).to receive(:method_missing).with(:cache_config).and_return({})
Everything in this statement has to be exact except...
replace :cache_config with the name of your custom configuration key
replace {} in the and_return({}) with the mock value you want to set for the key
Longer answer:
If you want to dig into why this is, since it is not at all obvious, check out the code at...
Custom class in Rails::Application::Configuration
Creation of #x for custom configs in Rails::Application::Configuration

In case this helps anyone using Mocha, I ended up doing this in a helper
Rails.application.config.x.stubs(:my_config).returns(true)
yield
Rails.application.config.x.unstub(:my_config)
The method_missing trick above didn't work for some reason (guessing to do with differences in the way the stubbing is implemented), failing with an error message about the Configuration::Custom class not responding to method_missing the first time it was referenced for a different piece of config.

Adding to the above answer(s), if you want to stub nested config, then you have to make sure that you're allow-ing the second-last piece in the config to receive(:method_missing), as such:
allow(Rails.application.config.x.foo.bar).to receive(:method_missing).with(:foobar).and_return(:baz)
The above code stubs the Rails.application.config.x.foo.bar.foobar config to return :baz.

Related

Accessing models and modules in a Rails 7 initializer

This issue relates to a need to set a Rails config variable as the application boots, and the value of that variable needs to come from data in the database (which are then modified). So, I have an initializer with something like this:
require "#{Rails.root}/lib/modules/facet_altering.rb"
include FacetAltering
Rails.application.config.reject_subjects = FacetAltering.reject
The reject method is potentially slow and calls the Subject model (which includes some concerns).
If I try to require subject.rb, application_rb and the relevant concerns from app/models then I progress a bit further, but eventually get stuck on uninitialized constant Subject::MySpecialConcern.
There might be some better way to set the reject_subjects value; I'd prefer not to run FacetAltering.reject each time the value of reject_subjects is used, though this might be an easy 'fix' if no other solution arises (at the cost of slowing things down). Or, is there another way to access these classes as the application boots?
Edit: Following on from the comment below, this is in config/application.rb:
%W[#{Rails.root}/lib/modules #{Rails.root}/test/mailers/previews].each do |path|
config.eager_load_paths << path
end
This post offered a useful clue:
Rails Model no longer available in initializer after upgrade to Rails 7.0
So, putting my code in config/application.rb as follows did the trick:
config.after_initialize do
Rails.application.config.reject_subjects = FacetAltering.reject
end
Now to find the answer to RuntimeError: Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. and I might be able to complete this Rails 6 -> 7 upgrade!

Custom Rails initializer as a class

I have an initializer to load some data from a csv file to a global variable to be used in a model. Right now it looks like this:
...
XYZ = Hash[*CSV.open...]
Now I want to write an rspec spec for this initializer. I am using rubocop-rspec and it says that it is that describe should take not a string, but a class or a module (https://www.rubydoc.info/gems/rubocop-rspec/1.1.0/RuboCop/Cop/RSpec/DescribeClass).
Assuming this is reasonable, what are the options to convert initializer to a class?
There is an option to use describe XYZ but it does not seem right.
1) Well, you could wrap Hash[*CSV.open...] into some helper class or module, say, DataLoader, cover it with tests and then invoke like XYZ = DataLoader.do_stuff(filename) in your initializer.
But there is one "issue" with this approach: the class will be really "dumb" (adding no custom logic at all, just wrapping a couple of methods from stdlib) and will be used in an initializer only - so will be called just once. I doubt all this additional boilerplate worth it.
So, I'd probably consider something simpler:
2) Just ignore (disable) this particular cop for this particular case. Conventions are good and rubocop is just great, but sometimes breaking the rules leads to a cleaner code than blindly following them. For example, I sometimes add tests for destructive rake tasks - in this case, I have to break this convention too, and I do it when necessary because it is still better than artificial workarounds for the sake of conventions... Need to test a minor piece of initializer's logic? Just do it :)

Ruby getting undefined Method error, and i am not sure if i fully understand how methods are working for my plugin

i am trying to create a plugin for Discourse, which is written in Ruby. As normal blank files my program is working perfect and without errors, but when i try to adapt my code into the plugin context i run into issues and i am not sure if i really understand how the whole idea with functions is meant to be.
I thought it would be smart to have more than just one file, to outsource functionality in different methods and require them in a kind of "main" file. For example getting tweets is one method in an extra file, sending tweets a different method in another file. In blank ruby code its working fine but when i try to integrate that into the plugin file structure i get the error
undefined method `my_method' for #<Plugin::Instance:0x00007f9004012fc0> (NoMethodError)
the files with the methods are in a lib directory and the "main" file which is called the plugin.rb is in the mainfolder
so i tried
require_relative 'lib/my_method'
and the other way
require_relative File.expand_path('../lib/my_method.rb', __FILE__)
but i still run into that error.
i have not defined any kind of classes or modules or something like that so the "method files" are literally starting with
def self.my_method
#my code here
end
Could that be the reason why i run into the error above? Why is it working as blank ruby code, but not when i try to run the plugin with rails s on my discourse instance?
I am still pretty new into ruby programming, so maybe my question seems a bit silly.
Here is the link which lead me threw the plugin creation:
https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-1/30515
Unfortunately, your understanding of methods is shallow. Basically, any method you declare in the global scope is added to the Object class as private method, so it is accessible everywhere in your objects cause they derive from Object class and in global scope because it is the scope of the Object class. If you declare method as self.method, you make it a method of main Object, because self refers to main, which is not the desired behaviour for you. To fix that issue, you should just remove self and write it like that:
def my_method
end
This way this method will be added to the Object class itself, not the main Object. There is a link on the article about methods in general. And another one on the toplevel scope behaviour. In this codepen you may observe the difference. Also, it may be useful for you to learn some Ruby before going on with your development. I suggest rubymonk. Another issue is your one-method files which is not the best practice for ruby code organization. Ruby is truly object-oriented language and if you need to have a bunch or even one general-purpose method, it is better to put it in a module or class to define its purpose and role in application, make it reusable and trackable, without global scope pollution.

Testing Rails helper that uses html escaping

I'm attempting test a helper in Rails 4 that calls h(some_content) but when I run my unit tests I receive: undefined method 'h' for PageHelperTest.
How can I call h inside my helper, but still be able to execute the code in test? The code works correctly when hit through the website.
The h method is defined on ERB::Util which isn't available in the helper test. I fixed the issue by changing the helper to call ERB::Util.h(some_content)
Well I just hit similar issue.
However changing the helper just for the tests to pass is a NO NO to me.
So I tried around and this solved it (HOWEVER in Rails 3.2.22):
include ERB::Util
alias_method :html_escape, :h
I guess it's not the cleanest way (the alias, and what if another helper needs some other thing). So I guess there would be some better way (include some group of modules or whatnot) - but this one works.

set configuration constants depending on environment in rails

I would like to define a constant (like the admin-email-adress) depending on the environment. What is the easiest way to do this?
I'd like something like that, in development.rb (or test or production.rb):
ADMIN_EMAIL = "foo#bar.com"
And be able to access it by calling something like
ADMIN_EMAIL
Is there an easy way or do I have to do something like creating a module and initialize it and stuff (and in case you're wondering if I have any idea about this, unfortunately: I don't)
It works this way, but one has to
restart the server, for the constants
to take effect.
In config/environments/, there are some configuration files that get executed based on what environment you're currently in. Try defining a constant in one of those.

Resources