Today I Learned

7 posts by jordanneville

Add have_selector() matcher to RSpec Request Specs

If you are used to writing controller specs, you are probably comfortable with the `have_selector matcher. However, in request specs this matcher is not available. By default, you can only do text search inside the request body which leads to brittle assertions.

You can add the have_selector matcher by updating your RSpec config to include the Capybara matchers on request specs as well.

RSpec.configure do |config|

config.include Capybara::RSpecMatchers, type: :request

end

Then you can write more confident Request specs by using assertions like expect(response.body).to have_selector('ul li', text: 'List content here!')

Assignment on associations will bypass validation

Important gotcha that assignment to has_many associations will cause an immediate save even if callback validation fails.

u = User.last

u.accounts = []

u.save # returns false because this user cannot have blank accounts

u.reload.accounts # returns empty array

The gotcha here is that save is a no-op really. As soon as u.accounts=[] is called, the data is saved immediately, bypassing validation.

Creating multiple objects with Factory Girl

If you need to create more than one object from a factory, you can use the create_list method to accomplish this.

FactoryGirl.create_list(:user, 10, :supervisor)

Will create 10 supervisor users with the supervisor trait.

React will conditionally batch calls to setState()

React tries to be smart and batch calls to setState() when its being called from a UI event context (e.g. button click). This has ramifications on code as your setState() call is no longer synchronous and accessing this.state will actually refer to the old state.

E.g.

this.state = { hello: false };
...
onClick() {
   this.setState({ hello: true });
   console.log(this.state.hello); //<=== will print false instead of true
}

However, if the setState is in a context not from a UI event, setState becomes synchronous

this.state = { hello: false };
...
changeState() {
   this.setState({ hello: true });
   console.log(this.state.hello); //<=== will print true!
}

There's more info here on the topic of batching setState calls: https://www.bennadel.com/blog/2893-setstate-state-mutation-operation-may-be-synchronous-in-reactjs.htm

Rails assignment operation implicitly updates DB

In Rails, you may fall into a trap where simply assigning a value to an ActiveRecord object's properties may cause a DB write immediately.

This will occur implicitly without callingobject.save()!

What's vulnerable? It appears that ActiveRecord objects that expose a property via association are vulnerable to this quirk. This won't happen for properties that have no association.

For example, given the following:

class MyClass < ActiveRecord::Base

has_many :children

attr_accessor :property_name

end

....

obj = MyClass.find(1)

obj.children = [child_one, child_two, child_three] <=== this will write to the DB immediately!

obj.property_name = 'value' <=== this is in memory only, the DB has not been updated

obj.save <=== property_name is updated in the DB now

This may cause issues if your save implies validations and the validations fail. In this case, the associated property was updated in the DB but the other properties were not because of the validation failure.

Yikes!

Learning lessons the hard way: git clean

I had a huge list of superfluous changes in git that I wanted to clean up. Most of them were additions so doing a git checkout wasn't going to work.

I followed some instructions online and ran: git clean -r -d -x

The trojan horse of this command is the -x flag which will delete all files in your .gitignore!

This led to a half day of setting up my dev environment again to recover all the lost environmental configurations deleted by this command.

Stay tuned for tomorrow's lesson: Adventures with rm -rf /

Re-run only failed tests through Rspec

In cases where large refactoring is taking place and there are multiple tests across multiple files failing, there's an easy shortcut built into Rspec that allows you to re-run your specs but only the ones that failed.

The command

rspec --only-failures



This allows for a tighter feedback loop to get failing tests green.

Setup

This functionality doesn't come for free and some simple, but required, setup is necessary. Details about what's required can be found here.

The quick rundown is your Rspec configuration needs some extra flags set:

RSpec.configure do |c|
  c.example_status_persistence_file_path = "failing_specs.txt" 
  c.run_all_when_everything_filtered = true
end

This is required so Rspec will output any failing specs to a file and then read from it when --only-failures is specified.

Permanently using this, it's also a great idea to add it to your .gitignore file.