Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
209 lines (144 sloc) 4.57 KB
# What is this?
This is a gem that changes Rails behavior to allow you to code more freely, without coupling
yourself to inheritance issues. Take a look at the examples to see what it changes.
# Installing
Configure the gem in your Gemfile:
gem 'rails-and-solid'
# Configuring
In order not to refactor Rails completely, the easiest solution is to just trick it
and make it believe you have the controller he wants, just trick him:
class ApplicationController < ActionController::Base
solidify
end
# Inheritance coupling in controllers
Ok. Time for some goodies. First example, let's unpimp :
[code]
RailsAndSolid.pimp :clients
class ClientsControl
def show(id)
@client = Client.find(id)
end
end
[/code]
Notice that *id* is the parameter *id* from your http request. No inheritance required.
Same old behavior. No http params appeared. You can unit test for real, without all
its inherited behavior.
Why? Am I really coupled to something when inheriting?
[code]
rails console
> ActionController::Base.instance_methods.size
374
> (ActionController::Base.instance_methods + ActionController::Base.instance_methods - Object.instance_methods - Object.private_instance_methods).size
560
> ActionController::Base.methods.size
353
> ActionController::Base.ancestors.size
36
[/code]
By default, when creating a new controller in Rails, you are coupled
with 374 public methods (*542 in total*), inheriting behavior from *36 different* sources.
# Session parameters
[code]
class ClientsControl
def show(current_user, id)
# if session[:current_user] exists, guess who is here?
end
end
[/code]
Again, unit testable. Non-http intrusive.
# Session parameters
[code]
class ClientsControl
def show(current_user, id)
# if session[:current_user] exists, guess who is here?
end
end
[/code]
# What about creating a client?
Well, just create it:
[code]
class ClientsControl
def create(client)
client.save
end
end
[/code]
# But you can't load, right?
I don't think so:
[code]
class ClientsControl
def show(loaded_client)
@client = loaded_client
end
end
[/code]
# But what about responses? Sessions? Http stuff?
Just bind to what you need, nothing more than that:
[code]
def logout(session, redirect_to, flash)
session[:current_user] = nil
flash[:message] = "Logged out..."
redirect_to.root_path
end
[/code]
# Internally: Helpers
Helpers are the classes that help passing parameters to those
methods by using a key based lookup algorithm. For example
if a 'json' parameter is received, one will use the Json helper
to return it:
[code]
class ApplicationController < ActionController::Base
protect_from_forgery
solidify
class JsonHelper
def initialize(controller)
@controller = controller
end
def message(x)
@controller.render :json => {'message' => x}
end
end
KEYS["json"] = JsonHelper
end
[/code]
Now you can receive json and use the message:
[code]
def some_action(json)
json.message("Well done!")
end
[/code]
You can use those auxiliary objects to compose any kind of behavior (not just rendering)
by just receiving the expected coupled semantic value instead of inheriting everything.
# Handlers
Handlers are not key-fixed parameter providers. For instance, the param lookup:
[code]
module RailsAndSolid
module Handler
class Param
def extract(controller, name)
controller.params[name]
end
def handles?(controller, name)
controller.params[name]
end
end
end
end[/code]
It will be asked whether it can handle this parameter and the provide it.
# TODO List
Help?
First, simple unit tests.
Units are split and behavior is composed. This was only to
show that its possible to achieve the same (and better results) with less coupling and a different
OO design approach. So what about start unit testing, providing new handlers, new helpers or
just doing a blog post on it? Refactoring trick_him would also be nice. Removing the
pimp method would be even better but that requires more refactoring on Rails internals,
not just tricking it.
Results from handlers "handles?" could be cached.
You can also implement DI support by correctly implementing di_instantiate(type) at TrickHim.
Finally, feel you can support named parameters in 1.8.7+ if not yet supported.
Everything is quite simple so far as this is just a concept
# Why not changing Rails internally?
One needs to know how all the mixin of Rails interact with each other (i.e. the 36 ancestors
of ActionController) in order to make safe changes. That is the coupling this proof of concept
is trying show how to avoid.