I don't know that this is so much a reminder of "What can be done with rspec-rails" as a note that, "If you do this will rspec-rails, you will also need to undo it."
Today's note: If you hack the application routes for a controller test, you have to reload routes.
The original problem
This all came down to a spec that was attempting to test a method in
ApplicationController. In our Rails 3 setup (rspec-core and rspec-rails 3.7.0), the tests only needed a monkey patched
ApplicationController to happen prior to the test:
it 'example do test' do
get :dummy_action #etc...
# validate dummy_method_actually_under_test did the thing
In Rails 4 with the same gem versions, we'd end up with the "No route matches..." which could be remedied by redefining the routes.
get 'application/dummy_action/:id', to: 'application#dummy_action'
Great! The test passes now! (Insert philosophical argument about whether the person writing the test should have tested a method in this way.)
..until you run the rest of the suite, of course. Now *every* subsequent controller test has a "No route matches" issue. (Insert philosophical argument about whether you should be writing controller tests.)
This should serve as a periodic reminder to clean up after your tests, which in this case is:
In our Rails databases, we use multiple Postgres schemas. This is partly for partitioning archive data from fresh data. On a new Rails 5 project, I started having tests fail once the models had the "archivable" concern added, which depended on an archive schema existing.
Ultimately, the problem is that the archive schema wasn't being created in the test database, because
rake db:test:prepare performs a
db:test:load which was loading the
db/schema.rb, which is completely ignorant of the Postgres-specific schema concept.
In order to fix this without attempting ugly hacks, the simplest solution was just to switch to using
structure.sql for the Rails schema dump.
config/application.rb, insert the following in your
Application class definition:
config.active_record.schema_format = :sql
If you haven't already added the additional schema(s) to your
schema_search_path in your
You'll need to run
rake db:migrate to force
structure.sql generation before running tests again. Also, be sure to switch from
structure.sql if you're committing this file.
RailsSetting stubbing problem
I have an area of functionality that is controlled by a
Settings class that is a subclass of
RailsSettings::CachedSettings (rails-settings-cached gem) and changing that setting for tests was interfering with the parallel_tests gem when I ran my rspec tests.
I tried namespacing the cache, making TTL 0, invalidating the cache, monkeypatching the lookup... Every convoluted solution possible... to try and get tests to play nicely together.
The humbling thing about programming is how simple actual solutions are compared the things the brain comes up with.
The solution? Wrap the access to the Setting in a layer of abstraction (which I had partially done already.)
And then in tests:
allow(ThatFunctionality).to receive(:enabled?).and_return(true) # or false
Trying again to stub RailsSettings
The class method approach is explicit, but once a large set of settings is needed, it can get unwieldy. If you haven't found a better strategy for wrapping settings values, you can go back to stubbing the : method. The caveat is that you need to save the original Settings behavior for any settings that you want to leave unchanged. By stubbing the : first to passthrough to .and_call_original, you can add additional stubs on top.
This was a total pain to locate, as the exceptions being raised were pointed to UserSession.find in Authlogic::Session::Persistence.
gems in question:
I noticed that the only meaningful difference between the two environments was the following instance variable in Rails.application.config:
irb> pp Rails.application.config
Commenting out this initialization in the environment allowed the application to work again.
config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new('name of log'))
Searching on "usersession syslog rails 4" results in the following issue as the third search result: Version 0.1.1 breaks Syslog::Logger setups There is a fix in master, but it doesn't seem to have been published. I had promoted activerecord-session_store to 0.1.1 because DEPRECATION WARNING: `#quietly` is deprecated in rails-4.2.0.beta4, but it looks like locking the gem at 0.1 will work otherwise except for noisy tests.
Sometimes, having done things several times before can make you miss the OBVIOUS.
to my ApplicationController to verify that Pundit was being used for authorization, I got the following error in rspec:
2) Jobs GET /jobs works! (now write some real specs)
Failure/Error: get jobs_path
undefined method `verify_authorized' for #<0x007ffb252ae338>
# ./spec/requests/jobs_spec.rb:6:in `block (3 levels) in '
Maybe there's a problem with my RSpec? Nope:
Started GET "/" for 127.0.0.1 at 2015-07-31 07:13:17 -0500
Processing by JobsController#index as HTML
Job Load (0.1ms) SELECT "jobs".* FROM "jobs"
Rendered jobs/index.html.slim within layouts/application (0.5ms)
Completed 500 Internal Server Error in 8ms
NoMethodError (undefined method `verify_authorized' for #<0x007fd40a3639f0>):
Okay, does ApplicationController have the method?!
irb(main):008:0> ApplicationController.new.methods.include? :verify_authorized
Ok, am I doing that wrong?
irb(main):010:0> ApplicationController.new.methods.include? :authenticate_user!
Sigh. *Rereads documentation.*
What's missing? Oh yeah, a simple `include Pundit' in the ApplicationController
After installing the Devise Security Extension to add password expiration to my Devise models, I started getting the following error on an RSpec test for a controller that does not perform authentication of the user:
Failure/Error: get :index
undefined method `authenticate?' for nil:NilClass
After a bit of digging, I found that the helpers.rb in the gem includes an additional
before_filter that needs to be skipped:
So while I'm skipping
authenticate_user! in my controller, I still needed an additional:
Interestingly enough, the controller itself doesn't break, just the tests. The downside is that I'm referencing two different Devise filters/actions just to not use them.
I've had the Rails AntiPatterns: Best Practice Ruby on Rails Refactoring (Addison-Wesley Professional Ruby Series) book on my Safari Books Online bookshelf for almost a year now, and finally started reading through it. It was "updated for Rails 3", and there doesn't appear to be a newer version, so I was a bit skeptical about how much I'd get out of the book.
This book has some overlap with Ruby Science, but it is more Rails-centric in its approach. Going through the book I found quite a few topics that I've not really read in-depth on in any other book or come across in a couple years of Rails development experience.
- Use of ActiveSupport::Concern included for specifying validations as part of a module.
- Discussion of Rails plugins (which later were mentioned as falling out of favor)
- serialize :value, Hash vs checkbox/boolean model
- How Rails helps in the building of Semantic HTML
- Gems Clearance and AuthLogic as simple authentication gems.
- MVP pattern and ActivePresenter (looks like Rails 3 was the end?)
- Emphasis on controller as a representation of a resource
- Rails responders for changing respond_to to respond_with(object)
- rescue *EXPECTED_EXCEPTIONS (EXPECTED_EXPECTIONS as a constant array) to whitelist exceptions that are anticipated.
- Mechanize and RestClient as automated interface gems with other sites.
- render :status => :unprocessable_entity (or :created and other symbols that represent HTTP status codes)
- Strategies for testing rake tasks, including FileUtils::NoWrite
- sqlite3 ":memory:" database for gem testing
- Thoughts on when to add DB indexes
- AR association methods count vs. length vs. size and their behavior and resource usage.
- .flatten() as a code smell for ruby code that should be represented as SQL.
- Run rake db:migrate && rake db:migrate:redo for all migrations created.
- Don't go against AR bias against DB constraints except for NULL & default values.
- Never fail quietly, whether ignoring return values or overzealously catching all exceptions.
- rescue_from on controller to redirect specific exceptions.
I think the general concepts covered provided valuable background and food for thought even if not all the solutions are still the same. I don't regret the help examining current practices in my daily Rails development.
I ended up with this error because the class name I was referencing was
ClassNameXML but I was trying to call it as
Not following naming conventions combined with not calling a class by its properly capitalized name will trigger this. I burned an hour figuring this out. I hope you don't have to.
When I'm generating a migration, I usually don't go any deeper than specifying column_name:data_type on the command line, and often I only use the command line to generate the file itself.
I don't know that the other options really save all that much typing, but stopping to think about the other options for a few seconds might just help avoid having to go back later and add things.
Elementary things I don't make enough use of:
rails g migration AddXXXtoYYY automatically creates an add_column for table YYY for every column name specified.
rails g migration RemoveXXXtoYYY automatically creates a remove_column for table YYY for every column name specified.
column_name:data_type:index also adds an index to the new column.
rails g migration CreateJoinTableXXXYYY will produce a join table.
I ended up coming across this issue in Refinery CMS after switching databases.
I had forgotten that I had swapped out databases because I had moved on to changing some other configuration bits in following the RailsCast for Refinery CMS.
Fortunately, I found note on Refinery's Getting Started page that deals with this:
If you get a 404 error on the homepage, try running the rake db:seed and then restarting the server.