rails restful routing - single index with nesting - ruby-on-rails

I'm working on a rails application which is built around a tree data structure. As such, the index of the controller displays the root node of said structure. Demonstration is probably easier to explain what I want:
/place/1 == place
can I restfully define such that
/place/1/photos == place/photos
and
/place/1/photos/1 == place/photos/1
etc?
Ideally what I'd like is that the articles url and its nested resource urls work by default such that I don't need to change a whole bunch of stuff and conditionally generate paths all over the place.
Thanks in advance for any help. :)

You could have the usual nested restful routes (it's good to keep them as the baseline, and to use them for the non-sexy urls like update, create, etc) and then add some custom routes for your 'pretty' urls:
map.nested_photo "/place/photos/:id", :controller => "photos", :action => "show"
map.edit_nested_photo "/place/photos/:id/edit", :controller => "photos", :action => "edit"
you could then add a bit of logic into the photos controller to make sure you have the place even if you didn't get params[:place_id], eg
if params[:place_id]
#place = Place.find(params[:place_id])
else
#place = #photo.place
end
The main thing to watch out for in custom urls is that you don't create any conflict with a) the regular restful urls or b) your other custom urls.

Related

Multiple Layouts in One Ruby on Rails Site

I have a website that is already up and running. I would like to add a new page on my Ruby on rails Website and would like to make the page look completely different.
Different Layout, Different Theme, Everything.
I have created a new scaffold for these pages. but am unsure how to achieve the desired results.
You can create the assets for your theme to make the page look completely different. Rails uses the default app layout at app/views/layouts/application.html.erb
However, you can create another layout with your theme and render the view using:
render layout: "<your-layout-name>"
For different ways to render different layouts, you can also refer to this railscast
Yes, you can do this by passing some params from controller action and checking in application.html.erb
Suppose you have users_controller and home as controller action as your root path
Then in users controller
def home
params.store(:diff_page, true)
end
In application.html.erb
In your application content is the id for main div inside body then
#content{:class => (params[:diff_page] ? "back-black" : "back-gradient")}
back-black and back-gradient both are css classes which you can define different style and page, background image anything you want
You can also call different partial too, based on params
- if params[:diff_page]
= render :partial => 'users/new_page'
- else
= render :partial => "layouts/page"
We do this on one of the sites i help manage: we have two completely different domains, each of which with its own totally unique look and feel, running off the same rails app. The second domain is called "cmw" within our project structure, and is set up as follows.
routes
You can put a condition on routes which looks at the request domain, and only runs that route if the domain matches the condition. So, we put all the cmw routes at the top of the routes file, all with the same condition. Then, underneath, the regular site routes. When we get a request to "cmwdomain.com", it will trigger one of the cmw routes. Requests to "maindomain.com" will fall through all the cmw routes, because they have the condition, which fails for this domain, and hit one of the routes below which are for the main site.
eg
#################### START OF CMW ROUTES: ALL HAVE A CONDITION ON THEM TO DIFFERENTIATE THEM FROM REGULAR ROUTES ###############
cmw = { :host => CMW_HOST_REGEX }
map.resources :licenses, :controller => "cmw/licenses", :conditions => cmw
map.resources :users, :controller => "cmw/users", :conditions => cmw
#REGULAR ROUTES
map.resources :licenses, :controller => "licenses"
map.resources :users, :controller => "users"
controllers
The above system sends all of the cmw requests through to controllers in the app/controllers/cmw folder. Normally, all controllers would extend ApplicationController. In our case, we have a CmwController which extends ApplicationController, and is kind of like the CMW-specific version of ApplicationController. This has its own default layout and various protected methods and helpers which are specific to the cmw site. Then, all the cmw controllers extend CmwController instead of ApplicationController.
views
To match the views to controllers, we have a folder app/views/cmw where all of the different cmw controllers' corresponding view folders live.
This system is nice and clean. The second "cmw" site has its own routes, controllers and views, but accesses the same models and database as the main site: two faces of the same app.

In Rails, won't url_for pick up the route from the routes file?

I have a situation where there are lots of models and I am using STI in rails to help minimize the creation of so many models that share similar attributes.
However, I am trying to dynamically generate routes without having to duplicate controller logic.
I am using url_for(controller: controller_name, action: :show) or whatever the url should be. However, I would simply like these routes to be listed in the routes file and not have to create controller files for each. But looks like url_for expects the actual controller file to be created. Otherwise, it would generate the wrong url (its generating some auth/failure url, which, I have no idea why it is doing). How can I make it pick up the route in the routes file and not have a separate controller file?
I would take a look at Rails Routing and become familiar with resources.
I believe you could use the one controller for the different models. So in your routes.rb:
resources :sti_model1, :controller => 'sti_controller'
resources :sti_model2, :controller => 'sti_controller'
resources :sti_model3, :controller => 'sti_controller'

Rails controller and routes configuration for website

I have an application in RAILS, it is composed of a set of API, a basic website, and an admin dashboard.
For the API routing I have no problems, as they belong to a model and a controller and are compliant with the RAILS RESTful pattern (a controller for each model, and a method for each HTTP method).
What I'm not comfortable with is writing routes and controllers for website.
The main website is at / so the default route is root :to => "home#index"and I have
Routes for the website pages which look like
get "home/index"
get "map/index"
get "api/index"
get "cgu/index"
get "legal/index"
Which I think it is not good, as I have a controller per view and I need to define a get for each views.
Now for the dashboard I tried a different approach.
It is at /dashboard, the default route is match "dashboard" => "dashboard#index" and here is few pages as an examples
get "dashboard/index"
get "dashboard/users"
get "dashboard/users_stats"
get "dashboard/routes"
get "dashboard/routes_stats"
get "dashboard/charts"
get "dashboard/financial"
So for the dashboard I have a massive dashboard_controller, which contains a def method for each dashboard pages. IE:
#dashboard/users
def users
#users = User.all
respond_to do |format|
format.html {render :layout => 'dashboard'}
end
end
the controller of the dashboard is at /controller but for views and assets I have put it in /folder/dashboard/
Here is 2 questions:
What is the best way to build the home website and dashboard ? Should I have a controller per page or a global controller where I have a method per pages ? (Which I find very convenient, less code).
How should I organize my routes to avoid to set a get "something/something" for each page ?
Or it is normal with RAILS that I have a route defined for each of my page ? I'm fairly new.
EDIT:
To clarify, the dashboard is built around an existing application with API that follow RESTFul Rails pattern:
resources :users
resources :routes
But the dashboard is not tied to any existing resources, it only do stats about those resources.
If you have custom controller action names, then yes, you'll need to define every route. If you use Restful routes, then you can define them easily as
resources :users
which will automatically create routes for actions: index, show, edit, update, create and destroy.
This might be helpful: http://guides.rubyonrails.org/routing.html
For your dashboard, which probably is bringing together a lot of resources, so they'll probably be custom methods. I'd suggest focusing on building your app by individual resource. Then, once you've defined them all, build your dashboard.
I agree with everything that other people have said here. You should definitely try to be more RESTful and create more routes like this:
resources :users
However, there is usually a controller that is not RESTful (usually called pages or static) that serves pages such as Privacy, About Us, etc, etc. For those routes, I usually do this:
['api', 'privacy', 'us'].each do |p|
get p, :controller => 'pages', :action => p
end
check this guide out if you haven't;
I think you haven't embraced the MVC concept of Rails.
For example, say you have "users". You should have users_controller.rb, users.rb (model), /views/users (view) under the app directory.
users_controller contains the index, show, create, etc default actions and your custom actions like stats
user.rb contains instance/static/helper methods
/views/users/ contains templates that corresponds to actions in the controller.

Rails: Difference between List and Index

I appreciate this is an incredibly noob question, but having Googled multiple combinations of search terms I regretfully am still in the dark. These things can be difficult when one doesn't know and so obvious when one does.
I have semi-home page where incoming customers can choose to see a queried list or do a handful of other things. This isn't a home page but a sort of mini 'switchboard' within the site.
The seven standard RESTful Rails controller methods are (as I understand them):
List # shows a list of records generated with .find(:all)
Show # shows details on one record
New # initiates new record
Create # saves and renders/redirects
Edit # finds and opens existing record for edit
Update # updates attributes
Delete # deletes record
What to use when some users need to see a selected 'list' of records that isn't literally .find(:all)? How would this work given I still need a list function that gives me .find(:all) for other purposes?
I've heard of 'index' being used in Rails controllers, but I don't know the difference between index and list.
For best practice and best design, what controller methods would you use for a mini-switchboard (and other intermediate pages such as 'About Us')?
Any specific answers would be a bit more useful than links to http://guides.rubyonrails.org/action_controller_overview.html etc. :) Thanks very much.
First, I think it's important to note that the "standard methods" are neither standard nor methods in a sense. These are considered actions, and are only standard in that they are the conventions used with scaffolding. You can create any number of actions and group them logically with a controller.
If you open up [Project]/config/routes.rb and read through the comments, I think you'll understand a little better how controllers and actions map to a specific route. For instance, you can create a named route to the login controller's login action and call it authenticate by adding to the top of your routes.rb:
# ex: http://localhost/authenticate
map.authenticate 'authenticate', :controller => 'login', :action => 'login'
# map.[route] '[pattern]', :controller => '[controller]', :action => '[action]'
# ex: http://localhost/category/1
map.category 'category/:id', :controller => 'categories', :action => 'list'
# ex: http://localhost/product_group/electronics
map.search 'product_group/:search', :controller => 'products', :action => 'list'
To partially answer your question, you may want to consider adding a category model and associating all products to a category. then, you can add a named route to view items by category as in the code block above.
The major benefit to using named routes is that you can call them in your views as category_url or category_path. Most people don't want to do this and rely on the default route mappings (at the end of the routes.rb):
# ex: http://localhost/category/view/1
map.connect ':controller/:action/:id'
# ex: http://localhost/category/view/1.xml
# ex: http://localhost/category/view/1.json
map.connect ':controller/:action/:id.:format'
The key thing to mention here is that when a URI matches a route, the parameter that matches against a symbol (:id, or :search) is passed into the params hash. For instance, the search named route above would match a search term into params[:search], so if your products have a string column called 'type' that you plan to search against, your products controller might look like:
class Products < ApplicationController
def list
search_term = params[:search]
#products = Product.find(:all, :conditions => ["type = ?", search_term])
end
end
Then, the view [Project]/app/views/products/list.html.erb will have direct access to #products
If you'd really like an in-depth view into Ruby on Rails (one that is probably 10 times easier to follow than the guide in the link that you posted) you should check out
Agile Web Development with Rails: Second Edition, 2nd Edition

Validate no routing overlap when creating new resources in Ruby on Rails

I've got a RESTful setup for the routes in a Rails app using text permalinks as the ID for resources.
In addition, there are a few special named routes as well which overlap with the named resource e.g.:
# bunch of special URLs for one off views to be exposed, not RESTful
map.connect '/products/specials', :controller => 'products', :action => 'specials'
map.connect '/products/new-in-stock', :controller => 'products', :action => 'new_in_stock'
# the real resource where the products are exposed at
map.resources :products
The Product model is using permalink_fu to generate permalinks based on the name, and ProductsController does a lookup on the permalink field when accessing. That all works fine.
However when creating new Product records in the database, I want to validate that the generated permalink does not overlap with a special URL.
If a user tries to create a product named specials or new-in-stock or even a normal Rails RESTful resource method like new or edit, I want the controller to lookup the routing configuration, set errors on the model object, fail validation for the new record, and not save it.
I could hard code a list of known illegal permalink names, but it seems messy to do it that way. I'd prefer to hook into the routing to do it automatically.
(controller and model names changed to protect the innocent and make it easier to answer, the actual setup is more complicated than this example)
Well, this works, but I'm not sure how pretty it is. Main issue is mixing controller/routing logic into the model. Basically, you can add a custom validation on the model to check it. This is using undocumented routing methods, so I'm not sure how stable it'll be going forward. Anyone got better ideas?
class Product < ActiveRecord::Base
#... other logic and stuff here...
validate :generated_permalink_is_not_reserved
def generated_permalink_is_not_reserved
create_unique_permalink # permalink_fu method to set up permalink
#TODO feels really ugly having controller/routing logic in the model. Maybe extract this out and inject it somehow so the model doesn't depend on routing
unless ActionController::Routing::Routes.recognize_path("/products/#{permalink}", :method => :get) == {:controller => 'products', :id => permalink, :action => 'show'}
errors.add(:name, "is reserved")
end
end
end
You can use a route that would not otherwise exist. This way it won't make any difference if someone chooses a reserved word for a title or not.
map.product_view '/product_view/:permalink', :controller => 'products', :action => 'view'
And in your views:
product_view_path(:permalink => #product.permalink)
It's a better practice to manage URIs explicitly yourself for reasons like this, and to avoid accidentally exposing routes you don't want to.

Resources