Tuesday, July 26, 2016

React: Viewless components

I have been using Facebook's React library for a while now (https://facebook.github.io/react/) and I like it for the most part.

The most controversial aspect is the fact that the View is mixed into the Logic using what looks like HTML in the "JavaScript" file using JSX.

While it is possible to write React components in plain JavaScript, you must include a render() method.

However, it is actually possible to do the following...

(in ES6 syntax)

render() {
  return (null);
}

Now, why would you want to do such a thing? I thought the whole point was to use React to tie together your view and logic layers (and shouldn't you be abstracting out your heavy logic to plain JS classes?).

Well, the main reason you might want to do it is because of React's component lifecycle.

In my case, I wanted to trigger some JS to run when the component was loaded, and I happen to be using Turbolinks and React-Rails in a Rails project.

This JS had no view component, but I could not rely on document.ready because of Turbolinks, and I didn't want to put an inline script tag in the page (because who wants to mix JS and HTML together?). I was however happy to use the react_component helper to add in a React component and then trigger it on componentDidMount.

It's also a handy way to add functionality onto some components which for whatever reason need to be rendered by the server (i.e. adding a JS slide show component onto a bunch of rendered divs without having to pass all the images into react_component).

Thursday, April 07, 2016

Testing React components with Jasmine in Rails with the react-rails gem

So in my latest project, we are using Ruby on Rails and the react-rails gem to implement our React components.

While react-rails is fine for the most part (and for simple components), there is a severe limitation in that you cannot use ES6 modules (i.e. using the import and export classes) because of the way asset-pipeline works.

Asset-pipeline precompiles all your JS into one mega JS file and therefore doesn't allow you to call individual JavaScript components in separate files (because they don't pushed to the server). This is a severe problem which I hope gets fixed soon, but in the meantime don't hold your breath.

There are workarounds using Browserify and/or Webpack, but they are messy (Google them for more info).

In any case, react-rails does allow simple React components in Rails apps and it mounts them all in the window/global namespace, so you can use a good 75% of React within a Rails application.

So now, we come to testing...

Facebook uses a testing framework called Jest (https://facebook.github.io/jest/docs/tutorial-react.html). It looks pretty good, but unfortunately does require the use of the import/export syntax...

But hey, React components are just JavaScript right? That means you can test them with any library (like Jasmine https://github.com/jasmine/jasmine-gem). Right?

Well, they are JavaScript, but they are also kind of closed off and it can be hard to actually unit test and mock them. I created a React component called DailyQuiz and for testing I tried to simply instantiate it normally.

var dailyQuiz = new DailyQuiz();

I was then able to see all the attributes on dailyQuiz in the console as if it were a regular JS object.

Until that is, I ran into setState in the code. Unfortunately, it gave me some guff about

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the  component.

So, basically, this means we can't treat a React component as a plain JS object because there is a lot happening behind the scenes. In a way this makes sense because React does a lot of calculations before outputting to the DOM, so these restrictions are probably needed.

So rather than trying to fake the whole mounting of the component, I decided it would be better to actually mount it. I used jasmine-jquery (https://github.com/travisjeffery/jasmine-jquery-rails) to help me manipulate the DOM with jasmine.

I will show you the code snippet and then talk you through it.
  
  beforeEach(function() {
    deferred = new jQuery.Deferred();
    spyOn($, 'ajax').and.returnValue(deferred);
    setFixtures('[div id="react-fixture"]
test[/div]
');

    dailyQuiz = ReactDOM.render(
      React.createElement(DailyQuiz, {
        url: "/",
        saveUrl: "/save"
      }),
      document.getElementById('react-fixture')
    );

    deferred.resolve(data);
  });

* note for this example I had to replace the < and > with [ and ] for rendering purposes... Blogger is giving me some hassle...

So basically I am using $.ajax with the promise syntax and then spying on it so I can intercept the ajax call in my spec. I then use jasmine-jquery's setFixtures to create a place to mount the component, I mount the component with plain JS (not JSX) and the resolve the AJAX call.

From there, you can actually call dailyQuiz and it will reflect the state of the component.

The downside of this approach is that the data you use to populate your component has to actually work (you can't just stub any old data through it). There may be a way to spy on the subcomponents, but I haven't found a way yet.

The benefits are that you can test the actual state of the actual component. It's just not a headless component (it needs to be mounted). You can also use jasmine-jquery to test the DOM meets expectations.

Thursday, March 17, 2016

Rolify, Devise, Rspec and Capybara Webkit in Rails

So I was using Rolify, Devise, Rspec and Capybara Webkit in Rails and I ran into a problem with the Warden login_as helper when trying to do a feature spec with :js => true (if you didn't understand any of that, you are probably on the wrong blog).

Anyways, while the I was able to log in an admin, Rolify was not able to determine the roles. This is in spite of the fact that I added a role in Factory Girl.

FactoryGirl.define do
  factory :admin do
    email { Faker::Internet.email }
    password "password"
    password_confirmation "password"

    trait :administrator do
      after(:create) {|admin| admin.add_role(:administrator)}
    end
  end
end

This worked fine with js turned off, so why was it not working now?

So the answer had to do with the following line in rails_helper.rb

config.use_transactional_fixtures = true

So I believe what happened in Capybara webkit is that it runs in a different thread and while Warden's login_as worked fine in a regular spec (as the admin object is passed into memory), the role information (which needs a database lookup) was not passed through and hence when I was checking that the admin was an administrator (current_admin.has_role? :administrator) it failed.

So here's the fix

1) Install the database_cleaner gem

2) set transactional fixtures to false
config.use_transactional_fixtures = false

3) Set up database cleaner as per http://devblog.avdi.org/2012/08/31/configuring-database_cleaner-with-rails-rspec-capybara-and-selenium/

4) in your rails_helper.rb
require 'support/database_cleaner'

Your Capybara Webkit Rolify specs should now work with the Warden helpers.

PS Sorry this isn't that well written. This is primarily a reminder for myself (I am still in mid project), but I thought I would post it in case anyone else ran into the same issue.

Thursday, July 02, 2015

Global Warming is a Year 2000 problem

Global Warming is a Year 2000 problem.

What I mean is not that Global Warming has been solved (far from the case) nor that it is a problem that we could only have solved 15 years ago (though that might be closer to the truth).

What I mean is that if we solve Global Warming, then people will think it was a hoax (or something we should not have worried about).

Think back to the glory days of the 1990s. Media was full of stories about how all the world's computer systems were going to fail because programmers tried to save space on their systems by dropping the first 2 digits off the year field. Power stations and ATMs would stop working. Global financial crises would ensue (well, that did happen, but for other reasons...). Basically we were going back into the stone age unless we chucked large amounts of money at software developers to fix our computer systems and by and large that did happen.

After we partied like it was 1999, apparently nothing happened. I am sure a couple of systems failed, but nothing major. We were all still alive and we still had electricity and we could still get money out of ATMs.

Post 2000, everyone now thinks the whole thing was just a bunch of hooey.

But maybe we did avert major disaster by all our hard work. We will never really know.

I hope to an extent the same thing does happen with Global Warming. There are differences of course. We have not got a set date where the planet will be uninhabitable. A date when we can look back and laugh at ourselves for being so silly.

Instead, we will look back at ourselves in shame for being so oblivious (and remember what things used to be like).

There's probably not much we can do on an individual level. We will need to elect people who are aware of the problem and reach consensus (easier said than done).

Let's just hope that in 7984 years we have a Year 10,000 problem to deal with ("Why on earth did they think they could save space by only allocating 4 digits to a year?").

Wednesday, June 17, 2015

Source Location, i.e. Where the heck is this method defined?

Just a short one for now.

Most of the time, if you are working on a well defined project, it's pretty easy to find where a function is if you want to change/tweak it. You can look in the class where it is defined and/or see if there are any mixins or decorators.

Sometimes, it can be downright impossible to tell.

Enter source_location

Fire up your rails console and put in the following (for a class method :search on User)

User.method(:search).source_location

if you have an instance method

User.new.method(:search).source_location

This should give you something like the following

["/Users/yourdrive/Projects/prokect/vendor/cache/thinking-sphinx-9b71ce0f7d9d/lib/thinking_sphinx/search_methods.rb",

 367]

This should at least give you some idea of what is going on...