nested resources and parameters to form_for - ruby-on-rails-3

I'm currently reading Beginning Rails 3. The tutorial creates a blog. There Users can post Articles as well as Comments to the Articles. An Article has_many Comments and a Comment belongs_to an Article. Inside routes.rb we have:
resources :articles do
resources :comments
end
Now in the 'comments/new.html.erb' file the first line looks like this:
<%= form_for([#article, #article.comments.new]) do |f| %>
Can someone explain to me why two parameters are needed in the array? What is the second parameter's relationship with a form and especially why is a new instance of a comment being created?
thanks,
mike

This is the syntax for nested routes. You're creating a new comments object to hold the input from the user. See here for the full explanation.

Related

Rails 3: trying to follow railscast #17 to add genres to my posts

I've followed along exactly like the Railscast describes but using "genre" instead of "category," but whenever I make a post the genre_id shows as NULL and I'm getting a WARNING: can't mass assign attribute genre_ids
I only had genre_id in my post model attr_accessible because that's the name of the attribute, however when I change it to genre_ids. I get the error
ActiveRecord::StatementInvalid in PostsController#create Could not find table 'genres_posts'
post form
<% for genre in Genre.find(:all) %>
<div>
<%= check_box_tag "post[genre_ids][]", genre.id, #post.genre.include?(genre) %>
<%= genre.name %>
</div>
<% end %>
post model
class Post < ActiveRecord::Base
has_and_belongs_to_many :genres
end
I don't have an answer, but here are the things you should check:
In your code, the line <%= check_box_tag "post[genre_ids][]", genre.id, #post.genre.include?(genre) %> should include genres, not genre:
<%= check_box_tag "post[genre_ids][]", genre.id, #post.genres.include?(genre) %>
What Ryan Bates (I love his webcast) doesn't tell is that you need changes on your database so that your models may be stored. The reason for that is, that on a relational database, a m:n relation is normally realized by a separate table that just stores that relation only. Have a look at the "Rails Guides: Has and belongs to many associations".
So you have to generate a standalone migration (see the Rails Guide for that) that creates that table that hold both ids to posts and genres in one table. The table should be named therefore genres_posts, and include the columns genre_id and post_id.
So depending on what you have done before, you may follow all the steps to reach a working example application. Good luck!

Undefined method unexisting_url

I´m a newbie with Rails3 and I´ve got a strange problem. After searching in google and in StackOverflow for a while I decided to write down my question.
I have a Competencia and a Partida model. Competencia has_many :partidas and Partidas belongs_to :competencia.
I´m working with nested resources and my code looks like this:
routes.rb
resources :competencias do
resources :partidas
end
partidas_controller.rb
class PartidasController < ApplicationController
def new
#competencia = Competencia.find(params[:competencia_id])
#partida = #competencia.partidas.build
end
def create
#competencia = Competencia.find(params[:competencia_id])
#partida = #competencia.partidas.build(params[:partida])
if #partida.save then #blabla end
end
end
views/partidas/new.html.erb
<%= form_for [#competencia, #partida], :url => competencia_partidas_path(#competencia) do |f| %>
<!--blabla-->
<% end %>
I know that it isn´t the right way to specify the url in the form_for helper (specially if I´m not using a custom action), but it was the only way I could work it out. When I wrote something like this: <%= form_for [#competencia, #partida] do |f| %> I´ve got this error:
Showing /Users/ks/rails/projects/chronos/app/views/partidas/new.html.erb where line #4 raised:
undefined method `competencium_partidas_path' for #<#<Class:0x00000101718548>:0x00000101713728>
When I checked the routes (rake routes) everything seems to be fine.
competencia_partidas GET /competencias/:competencia_id/partidas(.:format) {:action=>"index", :controller=>"partidas"}
POST /competencias/:competencia_id/partidas(.:format) {:action=>"create", :controller=>"partidas"}
new_competencia_partida GET /competencias/:competencia_id/partidas/new(.:format) {:action=>"new", :controller=>"partidas"}
Can someone explain me where the competencium name comes from?. What would be the correct approach to solve this?
The problem is that Rails assumes english grammar rules for pluralization. You can read more here: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html. You can customize the inflector or use English model names. I'm Italian and even when a project is meant only for Italian customer I prefer to use English names.
Rails tries to singularize you model name. In your case, it thinks competencia is the plural of a latin word. To add an exception, put the following in config/initializers/inflections.rb:
ActiveSupport::Inflector.inflections do |inflect|
inflect.singular "competencia", "competencia"
end

Rails 3 create new child for a specific parent

Hey all! Just joined up on stack overflow, as it has been a helpful resource while starting to learn about Ruby on Rails 3.
I can't seem to find one particular answer though, and maybe I'm barking up the wrong tree for all I know, but hopefully you folks can sort me out here. First some info on what I'm working with:
- In my web app I have 2 models: Projects and Tasks in a one-to-many relationship.
- Projects has many Tasks, and Tasks belong to Project
- Tasks IS NOT a nested resource, as users need to be able to see all current tasks, regardless of which project they are for.
- routes.rb therefore looks like this right now:
resources :projects
resources :tasks
In the project show view I display a list of tasks associated with that project. below that there is a link_to for creating a new task that looks like <%= link_to 'New Task', new_task_path, :class => "new-btn" %>. The link_to takes user to the new view for creating a new task. The rendered _form view starts with: <%= form_for(#task) do |f| %>.
Now, I think I need to pass the project id from the project show view, to the new task view: but, this is where I am getting lost and possibly, a bit mixed up.
Could someone please point me in the right direction: maybe to a resource outlining all steps involved in doing this, or maybe even provide an outline of the steps involved in the process here.
Many thanks!
You may create nested routes and still be able to show all tasks regardless of the project. Just make sure you have task resource defined later, like
resources :projects do |p|
resources :tasks
end
resources :tasks
Now in projects index or show view you can create link like
link_to 'New Task', new_project_task_path(project)
The task index view may be a little bit tricky. All depends on how you sort these tasks. If, for example, you show them sorted by project then you can create a link like
link_to 'New Task', new_project_task_path(task.project)
As for forms. In new action you have to get the id from params and put it into task object
if (params[:project_id])
#task.project_id = params[:project_id]
In form view you may create hidden field that will save this value
f.hidden :project_id
This will make it work, but you will not be able to go to the new task without providing project. Better solution it would be to create a select field with all projects. To accomplish that you should do the following:
in models/project.rb
def getProjectsList
projects = Project.all
projects.map do |p|
[project.name, project_id]
end
end
in controllers/application_controller.rb
def find_projects
#projects = Project.new.getProjectsList
end
in controllers/tasks_controller.rb at the begining
before_filter :find_projects, :only => [:new, :edit, :update, :create]
in views/tasks/_form
f.select :project_id, #projects
This way you can always select project and in case there is one given in params it will be already selected
If current_user returns User object then you should be able to call
current_user.projects
to get all user's projects.
Defining a relation between user and task may be working (though I am not sure this one).
#models/user.rb
has_many :projects
has_many :tasks, :through => :projects
In this case simply
current_user.tasks
should return user's tasks
There are many ways to do it. An easy one may be to add a parameter to you link and use it in the controller:
In your view:
<%= link_to 'New Task', new_task_path(:project_id => #project.id), :class => "new-btn" %>
In your tasks controller:
def new
#task = Task.new(:project_id => params[:project_id])
end
One way to pass the project ID from the project page to the New Task page is to add it to the query string on your url. Example HTML would look like this:
New Task
To get Rails to generate that HTML, you can do this in your ERB:
<%= link_to 'New Task', new_task_path(:project_id=>#project.id), :class => "new-btn" %>
Next, you need to pass the project ID from New Task page to the action that actually creates the Task. One way to do that would be to make a hidden input inside your form that contains the project ID so that it will be passed along with the other parameters when the form is submitted. The HTML would look like:
<input type="hidden" name="project_id" value="<%= params[:project_id] %>" />
To do this the Rails way, you can set the project ID in the new task action in the TasksController:
#task.project_id = params[:project_id]
and then do something like this in your view inside the form_for block (I'm not 100% sure on the syntax):
<%= f.hidden_field(:project_id) %>
Wow! Thanks for all the great info guys! I definitely learned a few neat tricks going through this.
Here is what I have working now:
routes.rb
resources :projects do |p|
resources :tasks
end
resources :tasks
I'm really happy to learn that that part is possible. Now I can enjoy the benefits of nested resource, but use the original non-nested routes for tasks, as well.
the link_to in show project view
<%= link_to 'New Task', new_project_task_path(#project), :class => "new-btn" %>
tasks_controller.rb new action
if (params[:project_id])
#task.project_id = params[:project_id]
end
new task form hidden field
<%= f.hidden_field :project_id %>
That all works great for adding new tasks to projects: but, showing a list of all tasks, related to all projects, that are related to the current user was a little bit trickier, and I wonder if there might be a better way than what I came up with:
tasks_controller.rb in the index action
#projects = Project.find_all_by_user_id(current_user)
#tasks = Array.new
#projects.each do |p|
p.tasks.each do |t|
#tasks << t
end
end
I'm using the "devise" and "cancan" gems for user management(both have been great!). The "current_user" above is simply what you would expect: the currently logged in user. Is this a reasonable solution, or is there a better way of getting all tasks for a user?
Just in case:
User has_many Projects, and Project has_many Tasks

Rails 3 - Nested resources and polymorphic paths: OK to two levels, but break at three

I'm trying to do a simple family reunion site with: "posts", "families", "kids", and "pictures". Ideally I'd like the routes/relationships to be structured this way:
resources :posts do
resources :pictures
end
resources :fams do
resources :pictures
resources :kids do
resources :pictures
end
end
In the models I have the necessary "belongs_to" and "has_many" relationships set between fams and kids. Fams, kids, and posts all are defined with "has_many :pictures, :as => :imageable" while pictures are defined as: belongs_to :imageable, :polymorphic => true
When trying to do link_to "Edit" and link_to "Destroy" in the pictures views I run into all sorts of _path problems. polymoric_path works fine at two levels, namely for posts-pictures and fams-pictures but it fails to handle the three level case of fams-kids-pictures. I'm guessing that it was not designed to handle the two levels of "imageable" objects above the picture object. Another issue is that in one instance the pictures controller has to handle a "one level" resource-nesting situation and in another it has to handle a "two levels" situation. Not sure how to approach this.
One thing I did try was to not nest resources more than one deep, per the Ruby Guides directions. I structured them like this:
resources :posts do
resources :pictures
end
resources :fams do
resources :pictures
resources :kids
end
resources :kids do
resources :pictures
end
This caused another set of problems with paths since the fam to kid relationship was no longer preserved. I also could not get polymorphic_path to function correctly accross all the different picture views.
So here is my main question: Does anyone know of a Rails 3 example/tutorial where nested resources, belongs-to/has_many, and polymorphic relationships are all put together, especially where it is not just the simple, two-level relationship that most examples show? (I'm fairly new to Rails and the Rails 2 examples I've found in these areas are confusing given my lack of Rails historical experience.)
Or can someone tell me how to structure the link_to EDIT and link_to DELETE statements for my picture views, as well as the redirect-to statement for my create, update, and destroy methods in my pictures controller?
Thanks!
Your code example that limited your nesting to 2 levels is quite near the answer. To avoid duplicate routes for fams->kids and kids, you can use the :only option with a blank array so that the 1st-level kids will not generate routes except in the context of kids->pictures, like so:
resources :posts do
resources :pictures
end
resources :fams do
resources :pictures
resources :kids
end
resources :kids, only: [] do # this will not generate kids routes
resources :pictures
end
For the above code, you can use the following to construct your polymorphic edit url:
polymorphic_url([fam, picture], action: :edit) # using Ruby 1.9 hash syntax
polymorphic_url([kid, picture], action: :edit)
Have been having this exact same problem for a while. I have it working now, but it isn't beautiful :S
From a nested monster like:
http://localhost:3000/destinations/3/accommodations/3/accommodation_facilities/52
Your params object ends up looking like this:
action: show
id: "52"
destination_id: "3"
accommodation_id: "3"
controller: accommodation_facilities
where "id" represents the current model id (last on the chain) and the other ones have model_name_id
To correctly render another nested link on this page, you need to pass in an array of objects that make up the full path, eg to link to a fictional FacilityType object you'd have to do:
<%= link_to "New", new_polymorphic_path([#destination, #accommodation, #accommodation_facility, :accommodation_facility_type]) %>
To generate this array from the params object, I use this code in application_helper.rb
def find_parent_models(current_model = nil)
parents = Array.new
params.each do |name, value|
if name =~ /(.+)_id$/
parents.push $1.classify.constantize.find(value)
end
end
parents.push current_model
parents
end
Then to automatically make the same link, you can merrily do:
<%= link_to "New", new_polymorphic_path(find_parent_models(#accommodation_facility).push(:accommodation_facility_type)) %>
Any pointers on making this solution less sketchy are very welcome :]
I can't speak for the polymorphic association problem (probably need more info on the actual error) but you are indeed headed in the right direction by defining your nested resources only one level deep. Here's a popular article by Jamis Buck that has become a reference and that you should probably check out.

rails polymorphic nested comments - cant delete them

here my git https://gist.github.com/774510
the problem is if I'm using method:destroy in the _comments.html.erb I get sent to articles/1/comments, where no route matches, furthermore the comment doesn't get deleted
if I use method:delete, I get routed to the right page articles/1, but instead of deleting the comment, my app creates a new one :/
I believe you need to tell rails that the comment is nested under articles OR just delete the comment in the comments controller:
1.)
# routes.rb
resources :articles do
resources :comments
end
2.)
# _comments.html.erb
<%= link_to 'delete' comment, :method => :delete %>