Skip to content

Instantly share code, notes, and snippets.

@tekwiz
Created April 26, 2010 06:12
Show Gist options
  • Save tekwiz/379033 to your computer and use it in GitHub Desktop.
Save tekwiz/379033 to your computer and use it in GitHub Desktop.
state_machine User do
state :new
state :normal
state :locked
end
state_machine Membership do
state :new
state :active
state :inactive
end
state_machine Account, User do
# interesting idea... thoughts on this?
end
class User
state_machine do
state :new
state :normal
state :locked
end
end
@hosh
Copy link

hosh commented Apr 27, 2010

So I'm coming from having worked more with functional programming and immutable data objects. Side-effects, then, are anything that goes beyond doing a single thing in a single function, and are generally anything that goes beyond manipulating those immutable data objects. We don't have immutable data objects with Rails, and the whole data persistence layer is backed by a DB. However, what we do have is RESTful architecture where we're using four HTTP verbs and seven actions to describe the interaction with Rails and the data object. From that point of view, any code that does more than index, show, new, create, edit, update, delete are side-effects

Examples:

  • In the Rackspace Cloud API, we have a RESTful resource called servers where we manage the data representing a server. This includes a convenience handle, the instance id, its flavor (RAM size), etc. However, actually initiating a build or a reboot are not directly manipulating the RESTful data, even though they are important. These are side-effects, and in some ways, we care more about the side-effect of build, reboot, shutdown, etc. The pattern in this case is that it is twiddling an external build process that is not under the control of the Rails app. When you put that logic into the model, then you're effectively putting controller-like logic (what to do, as opposed to how to do).
  • User signup activation manipulates the user signup. However, what we really care about is the side-effect of doing an email confirmation or an invitation. The "external device" in this case is the human being that you are confirming or inviting.
  • Comment moderation looks simple at the beginning. You either approve it or deny it. What happens when you start adding in spam filtering? Now you're going to feed the spam through an external code that returns either yea or nay.

These are all side-effects. I'm pushing around the idea that these don't really fit in the RESTful way of doing things. Rackspace Cloud Servers, for example, have you do a POST/PUT on an action endpoint to initiate those side effects, to distinguish them from purely RESTful stuff, such as manipulating the metadata.

When testing this stuff, you have the same problem you have when testing controllers. To test the controllers, now you have to create a whole bunch of valid objects and mock objects. In most controllers protected by user signup, you now have to set up a fake user just to see if it responds to CRUD actions; we're not trying to test the validity of the object, yet here we are, doing that. It gets worse if you add model logic that should be in model logic into the controllers. At that point, it is harder to run those particular logic in its own thread, or from the command line, because now you have to create a fake request just to manipulate it.

The same is true when you add controller-like logic into model logic, even in the case of manipulating a single model (such as single-model user activation).

In the above example I gave, the test would look like this:

describe UserActivation do
  describe "when sending email confirmation" do
    before(:each) do
      @user = mock(:user)
      @user.stub(:email) = "[email protected]"
    end
    it "should send a confirmation to the user's email" do
      UserActivation.activate!(@user) ...
    end
  end
end

(OK, I admit, this part is vague for me and needs more thought. For one thing, it occurred to me that the email notification itself should be declared inside the UserActivation and not in the controller).

I was talking to Sam Schenken-Moore about it. He said, he thinks those state machines only solve half of the problem, because you usually put together a state machine of some sort to handle workflow and lifecycles.

@hosh
Copy link

hosh commented Apr 27, 2010

This circles around the same problem domain and comes up with a different solution from a different approach: http://www.infoq.com/news/2009/11/restfulie-hypermedia-services

The states are kept in the model, but now we're linking it to the controller and adding some actions via hyperlinking into the resources directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment