Is It a Bad Idea to Reload Routes Dynamically in Rails

Is it a bad idea to reload routes dynamically in Rails?

Quick Solution

Have a catch-all route at the bottom of routes.rb. Implement any alias lookup logic you want in the action that route routes you to.

In my implementation, I have a table which maps defined URLs to a controller, action, and parameter hash. I just pluck them out of the database, then call the appropriate action and then try to render the default template for the action. If the action already rendered something, that throws a DoubleRenderError, which I catch and ignore.

You can extend this technique to be as complicated as you want, although as it gets more complicated it makes more sense to implement it by tweaking either your routes or the Rails default routing logic rather than by essentially reimplementing all the routing logic yourself.

If you don't find an alias, you can throw the 404 or 500 error as you deem appropriate.

Stuff to keep in mind:

Caching: Not knowing your URLs a priori can make page caching an absolute bear. Remember, it caches based on the URI supplied, NOT on the url_for (:action_you_actually_executed). This means that if you alias

/foo_action/bar_method

to

/some-wonderful-alias

you'll get some-wonderful-alias.html living in your cache directory. And when you try to sweep foo's bar, you won't sweep that file unless you specify it explicitly.

Fault Tolerance: Check to make sure someone doesn't accidentally alias over an existing route. You can do this trivially by forcing all aliases into a "directory" which is known to not otherwise be routable (in which case, the alias being textually unique is enough to make sure they never collide), but that isn't a maximally desirable solution for a few of the applications I can think of of this.

When do I need to restart server in Rails?

2021 UPDATE:

Since this answer is my most visible answer on StackOverflow and it's quite old, I thought it was about time to update it to go a little more in-depth. It's original info is not incorrect, but it's a bit too generic, and I feel I can explain it better with the knowledge I have now. The original answer is preserved below.

The way Rails works vs plain ruby is that it basically requires your files on-the-fly without you needing to add requires on the top of the file. There is nowadays a good relatively in-depth page about it on the official Rails guides here.

Starting on Rails 6, they introduced a new loader to manage the logic for automatically loading the source files, off-loading that work to a gem called zeitwerk. There is also a page on the Rails guides that explains how the new loader works.

The basic idea is something like this:

  1. Rails runs a bunch of config files (environment.rb, application.rb, boot.rb, the respective environments/<environment>.rb, config/routes.rb, initializers, etc). All the files ran in this starting phase are in the config directory, so if the changed file is inside the config directory, it probably will need a Rails restart.

  2. Rails then starts watching all the files under each of it's autoload_paths (by default each dir inside app) to check when any of them change. It also specifically watches for changes on the config/routes.rb file.

  3. Now armed with a nice route configuration, Rails knows to which paths to respond and with which controllers. Once you hit one of the configured routes, Rails will run your controller action.

  4. Whenever ruby sees a constant that it doesn't recognize, it calls the method const_missing. Rails overrides this method to get the name of your constant and use it to search for a file with the same name inside each directory of the autoload_paths.

  5. When it finds the file it then requires it on-the-fly, with the assumption that it will define the constant that triggered the const_missing, and then the code goes on.

  6. Next time the same constant is used, it will now be defined, so it won't even reach the const_missing method.

  7. Last but not least, IF the config cache_classes is set to false (the default on the development environment), then whenever Rails catches a change in one of the files it's watching, it will unset the constant associated with the file (that it knows from the name of the file).

So whenever you need to change anything that is loaded in that step 1, with the exception of the specially treated config/routes.rb, you need to restart Rails. Anything else, Rails will reload by that autoload mechanism unless it's set to cache the result.

In production it is also configured by default to preload the classes, so it will load all the classes before even starting the server. That's to avoid the overhead of the whole const_missing, file searching and dynamic require thing.

Non-ruby files, like assets and views are all read by Rails at the time of request, so you can always change them without restarting Rails. (Note: on production assets are usually pre-compiled, so changing the assets inside app/assets won't result in any change. But it is still loading at the time of request, it's just that the file in question is a compiled bundle inside the public directory).



Original answer:

You need to restart you server when you need Rails to be loaded again from the start.

If you're adding or removing gems, then yes, you will need to restart the server.

If you change your version of ruby, change your Gemfile or change something from internal classes of Rails, you will need to restart it, otherwise it should be ok. But if unexpected problems arise, restart the server is the first thing you should try.

Also, on a side-note, you will only see the changes just refreshing the page if config.cache_classes is set to false (which I think is the default for development, but not for production).

Edit:

Just to make sure everyone will notice, tadman said one wise thing at the comments, The general rule of thumb here is making changes to anything outside of app/ or config/routes.rb or db/ will require a restart.

How delete routes from memory in Rails

If you use Rails.application.routes.draw rather than Rails.application.routes.append you should be able to use the reload_routes! method to clear the contents. When you call reload after using append it is literally just reloading the routes that you have appended. If you draw the route it will never be appended to the routes object and you should be ok.

Is ruby on rails designed with full page refresh in mind

Asking on page-refresh is in the realm of the browser, while the rest of your question tends more towards server-side questions. So, I try to address the server-side.

The background of the framework design from Ruby-on-Rails is: CRUD which improves the way to select, insert, update and delete data across a network in a database.

Across a network means that you use HTTP and the primary ways to modify data on the server-side is by processing POST and PUT requests. As soon as you want to provide more than 1 action/page you may need to work with Ajax, and you maybe leave the Rails Way. For a use case where you want to combine a Todo List, a Profile Manager, a Scratchpad, ... without a full page rrefresh, you might better go looking into client-side JavaScript frameworks (Angular, Ember, Backbone being the popular ones in the Rails community).

You also might want to do some background reading where the opinions in Ruby-on-Rails are coming from, e.g. Why Rails is different from Java, you will see that:

  • 1/3 of Ruby-on-Rails is providing nice ways to deal with HTTP (= routing and controllers)
  • 1/3 of Rails is providing a better interaction with a database (= ActiveRecord)
  • 1/3 of Rails is better rendering/presentation of information (= ActionViews)

Rails 3: Links that Call Actions but Don't Leave/Refresh the Current Page?

There are 2 scenarios you normally have when calling an action from a view in Rails3:

  1. Using the normal link_to with its variations.
  2. Using link_to with the optional argument :remote => true (default is false)

The first one results in the the normal request / response cycle:

  1. Call the action in the controller.
  2. If there is nothing special done (render or redirect), just render the view that is related to the action. So if the action is called do_db_stuff, Rails3 tries to render do_db_stuff.html.erg (or do_db_stuff.html.haml`) in the relevant views directory.
  3. You may change that by calling explicitly render or redirect at the end of your action:
    • render: Renders the template you name, using the current state of the controller.
    • redirect: Redirect to a new action (of this or another controller), and using the relevant template to render then.

This is e.g. used when calling in the create action some save method, which fails, then you return to the same page by calling render :action => "new" # doen't execute the method

The second one just calls the action, and returns the result to the page. It is the responsibility of the page what to do with this. In a slightly different scenario, I have e.g. in my application something that goes like that:

  1. On the left side of the view, there is a list of tasks.
  2. To each task item, there is registered an on-click handler that calls search_task (by using JavaScript with AJAX) on the controller.
  3. The controller responds to that call by sending the result of a render :partial ... call back.
  4. The JavaScript function in the handler has registered a success function that looks like that:

    element.bind('click', function() {
    $('#left').children().removeClass('selected')
    element.addClass('selected');
    $.ajax({url: '/tasks/search_task/' + task_id,
    async: false,
    dataType: 'html',
    success: function(data) {
    target.html(data);
    }});
    }

    (have appended the whole click-function here)

  5. So the result (when successful sent) will result that the contents of target will be replaced with the sent HTML code.

I don't know if something similar is possible with link_to, perhaps there is an option.

So yes, :remote => true should work for you if you only want to ignore the result.



Related Topics



Leave a reply



Submit