I would like to set up a pair of style guides for the admin and public sections of a website.
Each will need its own layout which will contain a mixture of static html and calls to erb partials (so a static page won't cut it). I have no need of a controller(s) to serve these pages and I don't want what is effectively development-only content cluttering up the rest of the code. This got me wondering whether there is a way to render a layout directly.
Disclaimer: I appreciate this is not something I should do often/ever and I know there are a wealth of arguments for why this is a bad idea. I am interested in whether this is possible.
Is there a way for me to render a layout directly from routes.rb without going through a controller?
For some weird reason I wanted to render a blank JS file for a while, and writing a controller felt like too much for this kind of hack. Thanks to #genkilabs 's answer, I used this 3 liner:
get 'analytics/some_file.js', to: -> (env) do
[200, { 'Content-Type' => 'application/javascript' }, ['']]
end
I wanted to do something really stupid once, so if you do too, try this working example.
match :movedpage, :to => proc { |env|
if Rails.env.production?
#remote_path = 'http://productionhost.com'
elsif Rails.env.staging?
#remote_path = 'http://staginghost.com'
else
#remote_path = 'http://localhost:3000'
end
[
200,
{"Content-Type" => "text/html"},
[File.read("public/moved_page.html").gsub('#remote_path', #remote_path)]
]
}, :via => :all
Where moved_page.html was a static page asking people up update their bookmarks and #remote_path just typed in a link like #remote_path. Note that <%= %> won't work because you don't have view helpers in there.
So, theres enough rope to get yourself in trouble ^_^
Actualy the answer is NO, you can't do it without a controller. But see some trivial workaround...
It's not very fair but should work:
Assuming you have FooController with any logic you already have implemented. Now you want to render anypage.html.erb without creating any special controller. Here is how:
Configure a route to your static page:
get '/your/static/page', to: 'foo#anypage'
Implement the view app/views/foo/anypage.html.erb.
The problem is that it is impossible to change a path to your view. The path depends on the controller that you specify in a route (foo in the example). Also note that it will be rendered with a specified for FooController layout.
It should work by convention and you can read about it here.
UPDATE
Also I found very simmilar solution here. Using ApplicationController seems more reasonable for such pages. (Note that you needn't to create an action for it)
Related
I'm working on a personal RoR project with an interesting sort of problem: the whole app only needs one HTML template.
Basically, the whole app is presented through HTML5 canvas (it's going to be a game of sorts). But I'd still like there to be URLs for accessing specific resources, such as '/player/1'.
So what's the best, DRYest way to do this? I'd really hate to specify the template in every action in the controllers.
render :file => "layout_file", :layout => false
You could define your view in app/views/layout/application.html.erb and leave all the others empty, but that wouldn't avoid the reloading of pages.
You should also have all your methods respond in json format.
Or just an old good:
render :nothing => true
at the end of your methods.
I'm converting a bunch of landing pages written in php in order to add them to my RoR-based site (that's been live for more than 2 years now). These landing pages are divided into several versions, but unfortunately there is no consistency as far as URL names go. My problem is that the php pages I'm converting already have a high page rank, therefore I'd like to keep their URLs exactly the way it was.
I'm not sure how to set my routes.rb so that example.com/* will always go to my homepage; however, when (* == 'name-of-one-of-the-landing-pages') Rails will route to a separate controller, where a specific action will determine which page to render, based on an Initializer and the params hash, all this while the URL is, as mentioned, identical to what it was prior to the php-to-RoR conversion, namely www.example.com/name_of_landing_page, rather than www.example.com/*controller_name*/name_of_landing_page.
I know of the :path property that enables one to exclude the controller name from the path if passed an empty string (i.e. resources :examples, :path => ''), but that doesn't quite solve the entire problem.
I was thinking about writing an initializer that would hold a hash of all relevant landing pages, and using constraints in routes.rb to check against it, but I'm not sure if this kind of implementation is possible and how to go about it. A code example would be much appreciated.
Is there some kind of syntax for routes.rb that would enable me to do so, or perhaps a better solution?
To answer the first question: in routes.rb, inside the do/end block you will actually be in the context of ActionDispatch::Routing::Mapper, so no you won't. But, right after that block, you are back to the top level of your application and will have access to whatever variables you initialized inside your initializers, however, that code might be better suited to go in application.rb.
The only thing you should be doing in routes.rb is defining routes.
You could also handle the request for the legacy pages in rack
def call(env)
request = Rack::Request.new(env)
return [200, {"Location" => request.url("http://www.example.com")} if request.host == "www.oldpage.com"
end
More info here: http://railscasts.com/episodes/222-rack-in-rails-3
I don't understand why complex things involved. Setting such should be very simple in route.
Suppose your have a controller to handle static pages named "PagesController"
get 'name-of-one-of-the-landing-pages-a', to: 'pages#a'
get 'name-of-one-of-the-landing-pages-b', to: 'pages#b'
There is no need to add controller names in the path. You can control all of them.
Sorry, I thought I understood this, but now I have to re-evaluate my understanding of routes.rb. Hoping you could help.
A browser request goes to the Application Controller and the Controller tells what to show, right? - what erb file, database stuff, whatever...
In my routes.rb file I have:
root :to => 'static_pages#FAQ'
Until lately I thought what was happening was: routes.rb is looking at my static_pages_controller.rb file, looking at the FAQ method, and then seeing what to do. If there's nothing in the FAQ method - as is the case - then Rails does its magic and goes to my FAQ.html.erb in my View, the closest thing.
But even if I change the name of:
def FAQ
end
in my controller, or delete the static_pages_controller.rb altogether, it still goes to my FAQ.html.erb file. So does routes.rb not even look at controllers? Does it go straight to 'View' files? Thanks for any help.
static page are served first so that is why FAQ.html.erb is always served.
Also "A browser request goes to the Application Controller and the Controller tells what to show, right? - what erb file, database stuff, whatever..."
I think of it this way: The request first goes through routing, then to the controller for the resource in question, the controller being inherited from application_controller, will then query the model as needed, calculating variables as needed which it then uses in comiling the View page, which are compilation, gets sent as HTML.
Your ideas are essentially correct; Rails will attempt to render the static_page's controller's FAQ method, which implicitly renders the "FAQ" view, if no view (or other output) is explicitly rendered by the action.
What you're missing is that Rails will fill in the blanks if any one of the controller/action pieces is missing. All you need to do is define the view and Rails will assume that you're simply not bothering to define an empty action.
Starting a method name with a capital letter seems like a very bad idea in ruby, because ruby will treat any identifiers that start with a capital letter as a constant. The very first thing I would do personally is change 'FAQ' to 'faq' in my routes and controller code. If you really want to have 'FAQ' capitalized in the browser url, you can probably accomplish that via something like:
root :to => static_pages#faq, :as => 'FAQ'
To check that your method is getting called, use the logger:
def faq
Rails.logger.warn "faq method did get called after all"
end
I am trying to do something for hours and I'm stuck with rails routes.
So.. the idea is to have some even more user-friendly urls like for example /Laptops for a category and /Laptops/Apple-MacBook-Air-and-so-on. I should also use such links for simple pages like /MyDummyPage etc.
So my idea was to get the request_url and check if i can find the page myself. But it seems rails is initialising this request class after defining routes and right before calling the controller.
As you can see I am stuck and can't see any possible solution for my problem.
I will be glad if someone can help me.
Thank you in advance.
All the best!
(Whole thing revised)
If you want to allow dynamic matches along with normal restful routes, there are a couple options- (put it at the end of your routes or it will match everything)
match '*raw' => 'dynamic#show'
And in dynamic_controller.rb
def show
parts = params[:raw].split '/'
# do logic here to set all variables used in views
render #resource_or_page
end
You could also use the input in a search function and redirect to the first result of that search. Or return a 404 if there are no results.
def show
results = search_method_here params[:raw].sub('/', ' ')
if results.any?
redirect_to results.first
else
raise ActionController::RoutingError.new 'Not Found'
end
end
Also, for freindlier urls within restful routes, try out this: https://github.com/norman/friendly_id
I think its important to realize that people generally do not manipulate URLs by hand, and its nice to have readable urls, but its more important for them to be clear on what/where they are doing/going.
In response to your comment, I think you are mislead about routing. If you make 2 routes :category and :page, they match the exact same url, except one of them stores it in params[:category] and the other in params[:page]. To differentiate it, you would need to have a different amount of arguments matched like :category/:product or a namespace, or, perhaps, a restful route which specifies the MVC the route routes to.
I'd like to have a URL like this:
/payroll/region/1
and I'd like it to map to the Tasks Controller's payroll_list function. I'd also like to use REST. What's the best way to do this?
Many thanks!
Well I'd suggest you better go with the convention how Rails handles this. If you still insist on using such "strange" URLs and want to ignore the problems/headaches this can create during further development, then try to use Refraction.
I don't want to be rude but currently it seems to me that you did not understand why restful URLs are the way they are. Please do understand the design behind this first, then rethink your application/controller and routing design. I bet you will be enlighted.
In this example, your URL should probably be /regions/1/payrolls with map.resources :regions, :has_many => :payrolls. Then your payroll list would be rendered by the PayrollsController having a params[:region_id] - and that actually makes sense (and probably what you tried to achieve with your URL layout). Code snippet:
def index
if params[:region_id]
#region = Region.find(params[:region_id])
#payrolls = #region.payrolls
else
#payrolls = Payroll.all
end
end
If you still want to have a resource under a different named URL, use the following:
map.resources :regions do |regions|
regions.resources :tasks, :as => :payrolls
end
This will map the nested resources to the tasks controller using the named URL part "payrolls." But this probably does not work as you might expect because restful logic means you should handle the payroll model in the PayrollsController. Otherwise you might run into strange looking code. Maybe your design of the TasksController is just wrong? Rails will probably expect tasks to be handled over to your tasks controller although you name it payrolls. This can be confusing at least (however, it does not actually expect these being task models, so it will probably work).
BTW - Keep in mind: "restful" also means your application should answer to standard verbs on a resource, not just using "resourceful" routes. It's also about the GET, PUT, DELETE and POST http verbs, and of course the "edit", "new" etc default actions. Do not try to make your controllers big and complicated. Follow the motto "skinny controllers - fat models".
OK, so a better question, then might be this:
How can I get it so that I use your suggestion:
/regions/1/payroll
and have that map RESTfully to:
Tasks controller with index, new, etc that are prefixed by "payroll_"?
Like this: TasksController#payroll_index or TasksController#payroll_new