4

This is a more focused version of a previous question of mine around Sinatra's handling of route methods.

From my understanding of the source code Sinatra takes the method block within the route, and passes a new method containing the same body out i.e:

get "some/url" do 
   return "Hello World" # this gets taken out
end

So in this example the method body seems to get copied into a new method which is applied to the Sinatra object. I am just wondering why this happens, I tried going on their IRC channel but no one was there, and the mailing list is not that busy.

The main bulk of the source code that I am talking about in their framework is within base.rb around line 1180:

  def generate_method(method_name, &block)
    define_method(method_name, &block)
    method = instance_method method_name
    remove_method method_name
    method
  end

So is there any specific reason why they do this and not just reference the method itself?

The reason I ask this question is because the way Sinatra currently handles this it makes it impossible to have a method that has knowledge outside of itself, and breaks a classes encapsulation by just taking a single method without context.

Grofit
  • 17,693
  • 24
  • 96
  • 176

3 Answers3

4

As by the comments above, this generates a method. A proper method. If Sinatra would not remove the method again in generate_method, you could actually call it by doing something like send("GET some/url"). The question is, why does Sinatra remove this method again? Simple, there could be more than one handler per route:

get 'some/route' do
  pass if request.referrer == '/foo'
  "didn't come from /foo"
end

get 'some/route' do
  "did come from /foo"
end

Both methods have the same name.

As to your comments about subclasses and methods, this should work:

class MyApp < Sinatra::Base
  def content
    return "did come from /foo" if request.referrer == '/foo'
    "didn't come from /foo"
  end

  get('some/route') { content }
end

Or, when doing a classic application:

helper do
  def content
    return "did come from /foo" if request.referrer == '/foo'
    "didn't come from /foo"
  end
end

get('some/route') { content }
Konstantin Haase
  • 25,687
  • 2
  • 57
  • 59
  • I think the answer to this question has become clear, which was my misunderstanding that it was a first class method not an anonymous method. It still doesn't help the *actual* problem I have but that is within another question. – Grofit Dec 02 '11 at 09:45
  • One thing that has just come to mind... in your last example you show a way to access things outside of the scope of the anonymous method, which is great... that **IS** my problem, but by the looks of it, the only way to do so is by adding it to the helper or using set. This seems great for small things where the method block actually does everything you need, but when you have dependencies on other classes and instances it seems like you would be adding almost everything to a helper just to get it in scope. A question displaying the main problem I have is on one of the comments for KL-7. – Grofit Dec 02 '11 at 09:48
  • You should look into modular applications. – Konstantin Haase Dec 13 '11 at 19:18
  • Are you talking more about modular programming or modular sinatra applications? I presume the former as I am already attempting to do the latter without much success due to the lack of context. In the former though I am trying to make things modular so they are just loaded in at runtime and separated, the problem is that if I do this then I need some common contract between the modules so they can communicate... be it event based, service locator based etc, however it seems that with the way methods are used it wants me to write non separated code and just directly use consts – Grofit Dec 14 '11 at 09:35
3

My guess is that they want to have full-fledged method (with access to other instance and class methods) for every route, but don't want to pollute namespace. Method name is generated as "#{verb} #{path}" so if, for example, you have multiple routes with different conditions for the same path collisions are inevitable unless you remove method right after defining and storing it somewhere else. And that's exactly what they do. Method is unbound, but it's not a problem as they can later bind it to any instance of the class.

Note, it's only a guess. I'm not that familiar with Sinatra, so this implementation can have entirely different idea behind it.

KL-7
  • 46,000
  • 9
  • 87
  • 74
  • Maybe I am just not understanding correctly, but is it not possible to just has a list of routes which point to a method key? then that method key references the ACTUAL method, not a new generated one. This would satisfy the criteria of having multiple routes to same method. I just see this as an almost crippling problem for what appeared to be a brilliant framework/library to me. As it is almost impossible to do any business logic within these methods as you can only really make use of static/global objects, and that pretty much stops any OO practises. – Grofit Nov 30 '11 at 10:46
  • How do you mean 'actual' method? When you define route in sinatra you're passing path string, some options and finally a block to `get` method. There's no any method for this particular route at that time. The action takes place inside `generate_method` where new method with pretty ugly names like 'GET /' is generated and stored with some other options into some array. What you reference in the question as 'method body' and 'copying method' are actually 'block body' and 'converting block to method' respectively. – KL-7 Nov 30 '11 at 10:54
  • Btw, how does that prevent you from using OO? – KL-7 Nov 30 '11 at 10:57
  • Because if you were to put your routes methods within a class (i.e a controller from MVC approaches) then try and call anything from within the controller you would not be able to, as the ***this/self*** object is no longer the controller it is the Sinatra object (or wherever it puts this method). I put a post about this problem in another SO post, have a quick read and it should fully explain the problem that caused me to write this question. http://stackoverflow.com/questions/8225844/working-around-the-lack-of-context-in-sinatras-route-methods – Grofit Nov 30 '11 at 11:33
  • I didnt actually realise it was an anonymous method, that changes things and makes sense as to why it does what it does. I always thought of these get() as a wrapper around a local method to the object that is calling it. However even knowing this, the problem still exists, as if I were to make a class inheriting from Sinatra::Base and made a local method called my_get_method and just told my route to point to this it couldn't, as it has no idea of the scope outside of it, and that is the main crux of the problem for me. Is there a way to change the scope of the invoked method? – Grofit Nov 30 '11 at 11:37
0

I guess it just simulates instance_exec to support Ruby older than 1.8.7

Ian Yang
  • 1,091
  • 8
  • 20