A Proposal To Magically Remove 'params'
Brian Candler
B.Candler at pobox.com
Mon Sep 3 05:30:16 EDT 2007
On Sun, Sep 02, 2007 at 01:17:01PM +0200, ry dahl wrote:
> I have a magical proposal for the anti-magic web framework.
...
> Instead of using params we could make actions more functional by
> taking advantage of this cool programming concept called 'arguments'.
> An example
>
> class ProductController
> def show(id) # GET /products/show?id=12
> @product = Product.find(id)
> render
> end
>
> def index(page = 1) # GET /products?page=2
> @products = Product.paginate_all(page)
> render
> end
> end
Just to add a dissenting voice - I very much dislike this idea. I consider
Merb to be a simple framework, "what you would end up with if you started
writing a Mongrel custom handler" was I think how someone described it.
I don't like the idea of my code having to be *parsed* to add magic, and I
wouldn't be confident that it would work in all cases (e.g. what about if I
put controller actions in a module and mix them in? What if I define
controller actions dynamically at runtime, e.g. based on reflecting the
database?)
I would prefer, if this is done, that it be made as a plugin, so I don't
have to have it.
However I have a couple of observations to add:
(1) Bog-standard rdoc is able to parse method definitions, so it probably
doesn't need a heavyweight parsing library. Perhaps even rdoc itself could
be used.
(2) I can think of a much simpler way to get a similar effect: use
method_missing to define an accessor method the first time a parameter is
accessed. Outline:
def method_missing(arg, *rest)
if params.has_key(arg) and rest.size == 0
eval("def #{arg}; params[#{arg.inspect}]; end") # FIXME!!
return params[arg]
end
raise NoMethodError
end
As long as the method is defined in the controller class, not the controller
instance, then the eval is run only the first time it is used.
Of course, when you write
Product.paginate_all(page)
then 'page' still has the overhead of a method call, rather than
dereferencing a local variable, but I believe that would be very small in
the majority of cases. It would also be offset against the overhead of
preparing the correct argument list in the "magic" design.
Also, 'id' would need to be a special case given that it's a (deprecated)
existing method.
def id
params[:id]
end
> - params in its current state is slightly dangerous. Clients can pass
> to it whatever (key, value) pairs they want and Merb will blindly
> symbolizes the keys. These symbols are never garbage collected.
This is a separate problem. I think the solution is to leave the keys as
strings in the params hash; I guess they start out as strings initially
(e.g. from regexp matching or XML parsing). So no extra garbage is created
up-front, and indeed less work will be done up-front in converting
parameters to symbols which may not actually be used.
Of course, params['id'] still generates garbage unfortunately, as will
params[:id] if it is converted to a string internally, as I believe
HashWithIndifferentAccess from ActiveSupport does.
The solution I propose is something like Symbol#to_s which returns the
*same* frozen string object each time it is called. That's easily
implemented:
class Symbol
@@_stringreps = Hash.new { |h,k| h[k] = k.to_s.freeze }
def to_fring
@@_stringreps[self]
end
end
irb(main):008:0> :foo.to_fring
=> "foo"
irb(main):009:0> :foo.to_fring.object_id
=> -605563374
irb(main):010:0> :foo.to_fring.object_id
=> -605563374
irb(main):011:0> :foo.to_fring.object_id
=> -605563374
irb(main):012:0> :bar.to_fring.object_id
=> -605617824
irb(main):013:0> :bar.to_fring.object_id
=> -605617824
irb(main):014:0> :bar.to_fring.object_id
=> -605617824
Regards,
Brian.
More information about the Merb-devel
mailing list