Rails 3 create new child for a specific parent - ruby-on-rails-3

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

Related

Rails 5.1 (postgresql): efficient DB query to select parent records and filtered children records

I'm working on a task-management system where a project has many tasks and a task can be assigned to users.
I want to add a "My Tasks" page that shows all the projects with the current user's tasks beneath.
Like this, where each task is assigned to the current_user.
Project #1
- task 1
- task 2
Project #2
- task 1
- task 2
Project #3
- task 1
- task 2
- task 3
What I'm trying to achieve with pseudo-ActiveRecord code:
#projects_with_tasks = current_user.projects.includes(:tasks).where(tasks: { user_id: current_user.id })
And then I would like to iterate over each project, listing the tasks assigned to the current_user:
<% #projects.each do |project| %>
<%= project.title %>
<ul>
<% project.tasks.each do |task| %>
<li><%= task.title %> - <%= task.due_date %></li>
<% end %>
</ul>
<% end %>
It seems simple enough, but when I call project.tasks it goes back and loads ALL the tasks for the project, not just the ones for the current_user.
Is there a way to efficiently get the project and filtered list of tasks?
The best solution I have at the moment is grabbing all the projects first and then iterating over them and making a separate DB query to retrieve all the filtered tasks. However, if someone has 20+ projects they are involved in (likely in my use case), then that's 21+ queries (1 for all projects and then 1 for tasks). Never mind the case where some users will have 50 projects...
I prefer to keep everything in ActiveRecord, but I also know this may be a case to create a query object that with some SQL.
If your models are defined like this:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :tasks
end
# app/models/task.rb
class Task < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :tasks
end
You can query projects with filtered tasks with this query:
#projects = Project.includes(:tasks).joins(:tasks).where(Task.table_name => { user_id: current_user.id })
.includes(:tasks) - to eagerly load tasks
.joins(:tasks) - to perform INNER JOIN instead of LEFT OUTER JOIN
.where(Task.table_name => { user_id: current_user.id }) - to filter tasks by user
You can also add scope to your Task model:
scope :for_user, ->(user) { where(user: user) }
After that, the query can be written like this:
#projects = Project.includes(:tasks).joins(:tasks).merge(Task.for_user(current_user))
The output will be the same.
NOTE:
I guess you won't do it, but still it's worth mentioning that you should avoid calling project.tasks.reload while iterating over projects loaded with any of the above queries. It will force reloading tasks association without filtering by user.
I'm assuming you have something like has_many :tasks in your User.rb file and a belongs_to :project in your Task.rb
Then you can simply fetch current_user.tasks.includes(:project) and then get unique list of Projects from there.

passing params through a link_to rails 3

Im just looking for some clarification on the following piece of code, well part of it.To give some background i have an app where you can upload recipes, search recipes and save them as favourites, this piece of code is in a controller "recipes", action is "my_recipes"
<%= link_to "Add to favorites", {:controller => 'favourites', :action => 'create', :recipe_id => recipe.id}, {:method => :post } %>
My understanding is that this creates a link_to (anchor tag if you will) that makes a post request through the create method within the favourites controller. This part I think i underdstand (corrections welcome), the part i am unsure of is
:recipe_id => recipe.id}
I know this is passing the recipe_id for example but I would like to know why we do this? and what relevance of the : before the first recipe_id.May seem obvious to some but you dont know until you learn.
Any help appreciated
Is this code in a partial? Is recipe being passed along? You should rewrite as so:
link_to "Add to favorites", new_favourite_path(recipe), method: :post
Do rake routes in your console and find out what the path is for creating favourites, then replace 'new_favourite' with that above. Note, the route might be identified with something more explicit like new_favourite_recipe.
To answer you question, you must pass recipe, or recipe.id because otherwise the controller wouldn't know which recipe to add to the favourites. You don't need to specify the user as that should be accessed directly from within the controller action using something like current_user.

Rails syntax Passing POST parameters from a form to a controller

I'm new to Rails (and fairly new to programming in general) and I am building a web app for myself as a way to learn. Right now I am modifying scaffolded forms and such.
My question is with the "create" method in one of my controllers. There are two entities I am concerned with: the User table and the Habit table. I created a dropdown box in the _form partial for the Habit views to allow a person to select a user from a list of all available when creating a habit as below
<%= collection_select :user, :id, #users, :id, :first_name %>
The habit controller, of course, has
def new
#users = User.all
...
end
This works fine, and when the form submits it posts two hashes of parameters :habit and :user. Now, when I want to process the form input in the create method, I'm not sure how to use the syntax correctly and assign the user_id to the newly create habit. What I WANT to do is something like this
def create
#habit = Habit.new(params[:habit], params[:user])
end
This, of course, is improper syntax.
def create
#habit = Habit.new(params[:habit])
end
assigns the params from the :habit hash correctly, but then the user_id is left unset.
What works is the following, but the code is very lengthy, assigning each value manually.
def create
#habit = Habit.new(:user_id => params[:user][:id],
:description => params[:habit][:description],
:habit_method => params[:habit][:habit_method],
:time_reqd => params[:habit][:time_reqd],
:will_reqd => params[:habit][:will_reqd],
:active => params[:habit][:active])
end
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
So my question is, when dealing with a form that posts data in multiple hashes, what is the proper way to pass those parameters into some method in a controller?
Instead of saying Habit.new( <lots of stuff> ), just use Habit.new(params[:habit]). Rails will try to assign each key in the hash (in this case, the params[:habit] hash's keys) to a matching value on the object.
Thus, if params[:habit] has a :description key, it will be assigned to a field called description on your model. This is called mass assignment and is quite handy.
Now you can just do:
#habit = Habit.new(params[:habit])
#habit.user_id = params[:user][:id]
You may want to read the RoR Getting Started Guide, like this section, for more similarly handy features of Rails.
Change
<%= collection_select  :user, :id, #users, :id, :first_name %>
To
<%= collection_select  :habit, :user_id, #users, :id, :first_name %>
The existing scaffold code should just work after that
Alternate
<%= f.select :user_id, #users, :id, :first_name %>

Beginner struggling with update_attribute command

I am in the process of trying to use the update_attribute command, but struggling to get it working (at all) and hoped someone could point me in the right direction?
I have previously posted a question about this issue, it was very useful in terms of giving a feel for the mechanics of what is going on, but unfortunately it didn't actually get it working.
I have a database of items (Items), which among other things contains ':item_name', ':click_count' and ':external_url'.
Currently I have a view (Showselecteditems) in which there is a list of all the items, when a user clicks on an item name, they are directed to the appropriate external url. This works using the code:
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url %>
I would now like to add the ability to count the number of times a particular item name has been clicked on (i.e. in total for all users, not individual users) and therefore the number of times each external url has been visited in order to work out which is most popular.
Reading around, I believe i need to modify the code above to something of the form:
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
And need to define this function somewhere - it seems to only be found if located in 'application_helper'?
def clickcountplusone
clickeditem = Items.find(params[:identifier])
clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
rescue ActiveRecord::RecordNotFound # to avoid error if no identifier value
end
Needless to say, I cannot get this to work... My question is therefore, how can I set things up correctly so that when the link is clicked on the count is incremented? The other common problem people seem to report is that the number will be incremented each time the page is refreshed, which I would like to avod if possible.
Previously people have suggested adding to the 'show' section of the 'Items' controller, however, i don't know how this would work as the links are being clicked on the Showselecteditems view page, not the database itself where you get the show, edit, destroy commands. Any advice greatly appreciated.
This
<%= link_to selecteditem.item_name.to_s, selecteditem.external_url, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
will not point user to the some_controller#clickcountplusone, because you already specified an external link.
The easiest way to do this job is to modify your link_to like:
<%= link_to selecteditem.item_name.to_s, {:controller => params[:controller], :action => clickcountplusone, :identifier => selecteditem.item_name} %>
And then to modify your actions source:
def clickcountplusone
clickeditem = Items.find(params[:identifier])
redirect_to clickeditem.external_url if clickeditem.update_attribute(:click_count, clickeditem.click_count + 1)
rescue ActiveRecord::RecordNotFound # to avoid error if no identifier value
end

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.