Custom Rails action and forms - ruby-on-rails

I'm still trying to get my head around actions and routes. I more or less understand how to user forms with the build-in controller actions like create, show, etc. What I want to do for a demo app is imitate a school's class schedule, where I have Courses and Students with a has_and_belongs_to_many relationship.
I'm using Mongoid, and I can add students to a course and vice versa using the console, but I can't figure out how to do it with a form. Would adding students to a course even be a controller action, or can I write and call a setter in the model somehow? If a controller action is better, what would the route look like?
If anyone knows of an example that does something similar, I'd love to examine it.
Thanks

It can be a controller action. If adding students to a course is a simple logic, you could add /courses/:course_id/Students/add. This means creating a courses folder, and a students_controller within it, with an add action.
Example (in your routes.rb)
resources :courses, :except => [:destroy] do
resources :students
end
More info: https://gist.github.com/jhjguxin/3074080
Is this what you are looking for?

Related

customized rails routing url

I am little bit confused in using rails route. I need some suggestion about customizing my url.
This is my current url
http://localhost:3000/posts/product/41?product_id=2
and
http://localhost:3000/posts/product/41?model_id=24&product_id=2
This is my link
<%= link_to product_model.name, controller: :posts,action: :product,product_id: params[:product_id],model_id: product_model.id
Logically product should come first in url. But why model prefers first here.
And i need my url something like this
http://localhost:3000/posts/product/41/mobile
and
http://localhost:3000/posts/product/41/mobile/nokia
Since i am not familiar with rails route i didn't write any special coding in my route
Here is the simple route exist
resources :posts
Ok your question here actually contains two different problems, so i will give suggestions to both.
1. Nested resources
Your first problem is to use "nested routes". Rails guide has a long and good article about routes and how to write and use them, including nested routes. You can check it out here: http://guides.rubyonrails.org/routing.html#nested-resources.
However in your situation would the solution look something like this:
resources :category do
resources :sub_category do
resources :products do
resources :models
end
end
end
You can now greate links like this
<%= link_to product_model.name, category_sub_category_product_model_path(#category, #sub_category, #product, product_model) %>
You can see that i have removed posts, see 3. Refactor design to see why. If you really want this as a action on posts, should you however do something like this (but would recommend this!):
get "posts/product/:category_id/:subcategory_id/:product_id/:model_id", to "posts#product", as: :posts_product
This would be used like this in your views:
<%= link_to product_model.name, posts_product_path(#category, #sub_category, #product, product_model) %>
2. Pretty URL's
Your second problem is to use model names instead of id's in your urls. The simpels solution for this is having a unique attribute on your model that you can use instead of id, and then just add a to_param method. Fx for product could we do something like this:
class Product < ActiveRecord::Base
def to_param
name
end
end
Ryan Bates have made a good screencast about this: http://railscasts.com/episodes/63-model-name-in-url-revised. If you want something more flexible should you use the gem Friendly Id. And again does Ryan comes to the rescue with another great RailsCast: http://railscasts.com/episodes/314-pretty-urls-with-friendlyid.
3. Change design
Ok well so this is just my opinion, feel free to ignore it. But their is some bad practices and signs in your examples, so let me just quickly go through what i think you should improve on.
Restful actions
You should, when possible always avoid creating controller actions that is not restful (simply put is the base actions index, show, new, create, edit, update and destroy the only restful actions). In your example does this mean that the product action of the posts controller should be changed to something restful. Why not move it to the product model controller and call it "show"?
Deeply nested resources
You should avoid nesting your routes to deeply. Is it really important to show both the category, the sub category, the product AND the model in your url? Maybe that's how your models are associated internally in your application but why should the user know this? If you don't have a list of subcategories at "/posts/product" and a list of products at "posts/product/41" is there no reason to have so long a route. A rule of thumb is "nest no deeper then two levels", ie. ":category/:sub_category". Further more does short routes mean better SEO.
As i said, feel free to ignore these suggestions, your application would work without these changes. However changing these things would greatly help you structure you code, and keep your codebase clean and maintainable. These rules and principles is not something i have just conjured out of nothing, but very accepted principles in the Rails community. You can google each of these principles or patterns and see a lot of articles and posts on why it's a good idea to follow them, especially when you work with Rails.
Resources
Rails Routing from the Outside In — Ruby on Rails Guides
norman/friendly_id - Github
#314 Pretty URLs with FriendlyId - RailsCasts
#63 Model Name in URL (revised) - RailsCasts
Take a look at the friendly-id gem
There's a great RailsCast about it
Add this to your model inmodel.rb
def to_param
name
end
and then add
#model = Model.find_by_name(params[:id]) to your show method, then you can get the url as you mentioned above.
PS: You Should have name field for Model table in your schema.
I think you are looking for nested routes. Please refer this link http://guides.rubyonrails.org/routing.html#nested-resources
and use to_param method in the model if you want to display model_name instead of id as explained by #Ajay Kumar
def to_param
name
end
where name is the model attribute for that specific model.
Why not a namespace?
namespace :posts do
resources :products
end
This should do I think..
Namespace does not include restful ids into the scope..

Rails route architecture

I'm trying to understand the best way to architect a fairly simple relationship. I have a Job Model and a Category Model with a has_many relationship between them in a JobCategories model.
I'd like to have a page that lists all Jobs for a specific Category. Should the logic to pull this data be on the Category Controller (on the show action), or should I create a category method on the Job Controller? My gut tells me it should be on the Category side because a Category has Jobs, but it doesn't feel right that a Job would have the logic to pull all the Jobs for a given category.
Having said that, if I want the URL to be something that is more Job specific like:
domain/jobs/:id/{category-name} (for SEO purposes)
How would I structure the route so that it reads like the above, as opposed to
domain/categories/:id
which is what you'd get with resources :categories, only: [:show]
Thanks!
For a pretty slug, I'd suggest using FriendlyId on your categories model.
As for the routes, you will not get the desired route using resources :categories
One way to do it would be
resources :jobs, only: [] do
member do
get '/:slug' => 'categories#some_action'
end
end
the slug will be passed in your parameters.
This will yield a route like this
GET /jobs/:id/:slug(.:format) categories#some_action
UPDATE
the :slug is just an example for pretty url.
In your case you'd want to have :category_name. That would be passed into your controller through the params[:category_name].
One thing that I did start thinking when I re-read your question is that you want to show a list of jobs for a specific category. A url path like /jobs/:id/{category-name} shouldn't actually show a list of jobs as you are specifying an id which means a specific job. I think the url that you're looking to get is more along the lines of /jobs/{category-name}. Am I correct?
UPDATE 2
I suggest you read this Ruby On Rails Routing
UPDATE 3
Since you did want an url more like /jobs/{category-name}
You're routes should look like this
resources :jobs, only: [] do
collection do
get '/:category_name' => 'categories#some_action'
end
end
Good luck with your project! :D

Rails - CRUD for user and admin, how many controllers?

My model holds expenses, including user and project references..
class Expense < ActiveRecord::Base
attr_accessible :amount, :expense_date, :description, :project_id, :user_id
belongs_to :project
belongs_to :user
end
The ExpensesController handles basic CRUD operations for the expenses.
I now am needing to build an administrators version of this same page, a new view preferably, which can include the different views of the data, by user, by project, etc, and can also edit data that the user cannot.
My question is: Do a build a second controller to handle the administrative perspective of the data, -- or do I setup conditions inside of every method, to detect the originating view and form, and then conditions to redirect them back to where they belong?
If I do build a second controller, how do I properly setup the form_for so that it knows what controller to go to?
Thanks!
PS - If anyone has any books about how to properly put together a rails app, I feel like I know the pieces ant parts, but I'm getting stuck on the big picture implementation. I learned rails with Michael Hartl's guide, prior to that I was a PHP developer.
IMHO, if security is a big concern for your app then using an admin namespace and separate controllers is the best way to make sure you don't leave any gaps. It's also just simpler and lower stress.
I would have a directory structure like so:
/app/controllers/application_controller.rb
/app/controllers/admin_controller.rb - inherits from application_controller
/app/controllers/expenses_controller.rb - non-admin, inherits from application_controller
/app/controllers/admin/expenses_controller.rb - inherits from admin_controller
Your views would be similarly separated/duplicated:
/app/views/expenses/* - non-admin expenses views
/app/views/admin/expenses/* - admin expenses views
In application_controller you'd put the Devise methods to authenticate_user and CanCan method to check_authorization (which throws an exception if authorization is not checked at some point in the controller action). In admin_controller you have more strict filters to make sure the user is an admin. Then you can get even more fine-grained in the specific controllers and their actions.
Of course each controller only has to define the actions it really needs and you don't have to duplicate views. Maybe the non-admin expenses_controller has index, show, new, create, while the admin one has only edit, update, and destroy. Then in the 'show' view you'd still have code that add links to the 'edit' action if the user is an admin.
Edit - Routes
With the above example, your routes.rb would look something like:
resources :expenses, :only => [:index, :show, :new, :create]
namespace :admin do
resources :expenses, :only => [:edit, :update, :destroy]
end
So you still use expenses_path() for the index and expense_path(foo) for show. A form on the admin page, however, would post to admin_expense_path(#expense).
If you wish to add another Controller I would suggest having common code in a module and importing them in each Controller. Each controller would also have an adequate before filter to check for adequate rights.
But I would prefer having one controller and one set of views since I think it avoids code duplication and/or confusion. Pass the user as a local variable to the view and check for administrator rights when you need to decide the URL for the form_for or whether to hide or show some part.
If the views differ significantly, check for admin rights in the controller and render either the user view or the admin view.
You can even create a special partial for the admin part of the view and decide whether to render it in the view or not, sending the appropriate data in the params or not.
You can use Devise gem for authentication.
You can create somekind of "namespace" for administrative part of your application.
I my opinion, creating another controller depends on how many views and actions your admin will access.
About the doubt about form_for action, it will be managed by your routes and paths that you configure in your form_for params.

Nested resources or not for categories?

I have an application, built with Rails 3.1.3, that has products and categories. The categories are related to other categories, so a category can be a parent or a child category. The products are then related to a child category.
Now, I'm thinking about how I should define the routes. Is it a good idea to somehow nest the categories and products resources? Ideally, I would like URLs like this:
example.com/parent/child/product-1234
like this:
example.com/clothes/underwear/some-socks-1234
or maybe like this to keep it restful?
example.com/p/clothes/c/underwear/....
But maybe that's a bit messy to achieve with the routes? I would have to nest the category with itself I guess?
Any ideas on how to achieve something like this?
EDIT:
Do I create the category routes like this:
resources :categories, :as => "parent" do
resources :categories, :as => "child"
end
or similar? It's not that important to have the product nested inside the categories. Maybe it will just make it hard to manage...
You can either be RESTful or not, you can't have it both ways.
If Category is a resource, then you can define routes with resource :categories statement and use Rails built-in support for REST. But then all categories should be accessible through categories/id. If you want different categories be accessible through different routes, then you are not RESTful and you can't use Rails REST support.
Alternatively you can say that parent and child categories are different resources and be RESTful again.
Edit: looking at my answer now (after it was accepted) I feel like I was probably too stiff. The answer should probably be "it depends" (as usual). If the application is CRUD (admin), I would stick to plain categories and nested products (shallow). On the storefront where you need nice looking url you can totally have non-REST routes and actions. It just mean more coding.
I would add an extra route besides the RESTful ones to support your URLs. This ignores the categories (they don't even have to exist) and routes directly to the product.
...
resources :products
resources :categories
match ':parent/:child/:product' => "products#show"
...
In your show action you can simply check which of params[:product] and params[:id] that is set and handle the different routes.
params[:parent] and params[:child] will also be available but i would suggest just looking for the product and get the category through that object since that should be more reliable.

Rails RESTful website vs user experience and less clicks

Situation: I have a team model, a user model and a teamate model for the users of a team.
Say I want to have a view that contains the information of a team team/show
and that I wish (to simplify the user's experience) to add a list of the users, an add user to team and the possibility to remove a user from that team.
To be perfectly restful, I would need a controller (let's call it Teamates), it would handle the users of a team.
I would have all the CRUD needed.
Is it clean to have the team/show view call the teamates controller for the following actions: adduser, removeuser, listusers.
What I am trying to achieve is less clicks for the users.
In other words, I would like the user to be able to manage the users of a team from the team view instead if requireing him to navigate even further.
I don't think you need a controller for teamates.
And you really should not have adduser/removeuser/etc actions in your team controller!
You could set up your routes like that:
resources :teams do
scope :module => "team_scope" do
resources :users
end
end
Then you would have a UsersController in app/controllers/team_scope/users_controller.rb
To create a new user for a team, you would post to: /team/1-team-a/users and it would hit the create action in the UsersController above.
When you use scope in your routes, it does not change the route helpers like with namespace. The new action would just be accessible via new_team_user_path(#team).
Hum... so yeah, in this case I would have a TeamatesController, and maybe set up my routes like that:
resources :teams do
resources :teamates, :only => [] do
collection do
get :edit
put :update
end
end
end
And then you could edit the associations between a team and its players...
Your form would post the users id to team_teamates_path(team)...
But I'm really not sure it's the best way, I'd have to think about it. This is not really restful as well.

Resources