nested resources with shallow routes in rails 3 - ruby-on-rails

I have been working with rails for a while but I have yet to overcome the problem of using 3 level deep nested resources. When I am on the notes page I would like to link course name to the class and course but rails keeps giving me an error.
I have 3 models class, course, and notes. A class has many courses and courses belong to a class. Course has many notes and note belong to a course. I will explain below.
class.rb
has_many :courses
course.rb
belongs_to :class
has_many :schedules
has_many :notes, :through => :schedules
note.rb
has_many :schedules
has_many :courses, :through => :schedules
schedule.rb
belongs_to :course
belongs_to :note
routes.rb
resources :classes, :shallow => true do
resources :courses do
resources :notes
end
end
index.html.erb
<% #notes.each do |note| %>
<% note.courses.each do |course| %>
<%= note_class(course) %>
<% end %>
<% end %>
notes_helper.rb
def note_class(course)
link_to course.course_name, class_course_path(class, course)
end
Shallow routes works great except when rails give me an error 'undefined local variable or method `class' for'. I think my code above is right but I am not sure why it is not working correctly. Any suggestions on how I can get the course to link to a url like so mysite.com/classes/1/course/3?

I realize this question was asked a while back but I figured since it hasn't been 'answered', I'll give it a go.
Couple of things. First, the error you're getting inside the helper method is a result of not having a reference to the class instance. You're only passing the course object as a parameter. This would work given your associations above
notes_helper.rb
def note_class(course)
link_to course.course_name, class_course_path(course.class, course)
end
Second, as mentioned in one of the comments, class is a reserved keyword in Ruby so it would serve you best to avoid using it for your models and associations. Cheers.

Related

How to create form for nested resource with has_many through association?

I'm having trouble creating the correct form_with syntax to create a record for a has_many through association and cannot find an example to copy.
My model has a Factory. Factories can have Equipment. Equipment can have many EquipmentVariations. Variations are specific to a particular type of equipment, but are different for each specific piece of equipment (so the VariationType is associated with the EquipmentType)
I can create the factory (e.g. localhost/factories/1)
I can create the equipment at the factory (e.g. localhost/factories/1/equipment/1)
But I cannot manage to make a form that creates EquipmentVariations. That is, when I navigate to localhost/factories/1/equipment/1 I want a form to add EquipmentVariations to that Equipment entry.
Here's my code:
routes.rb
resources :factories do
resources :equipment do
resources :equipment_variations
end
end
resources :equipment_types do
resources :variation_types
end
Models
class Factory < ApplicationRecord
has_many :equipment
end
class Equipment < ApplicationRecord
belongs_to :factory
belongs_to :equipment_type
has_many :equipment_variations
has_many :variation_types, through: :equipment_variations
end
class EquipmentVariation < ApplicationRecord
belongs_to :equipment
belongs_to :variation_type
end
class VariationType < ApplicationRecord
belongs_to :equipment_type
has_many :equipment_variations
has_many :equipment, through: :equipment_variations
end
And the view in app/views/equipment/show.html.erb
<h1><%= #equipment.equipment_type.name %></h1>
<h3>Add Variation</h3>
<%= form_with(model: [#equipment, VariationType.new], url: factory_equipment_equipment_variations_path, local: true) do |form| %>
<%= form.submit %>
<% end %>
This is as close as I have been able to manage, but gives the error: No route matches {:action=>"index", :controller=>"equipment_variations", :factory_id=>"2", :id=>"3"}, missing required keys: [:equipment_id]
Basically, I need to be able to post to the URL /factories/1/equipment/1/equipment_variations from the page at /factories/1/equipment/1. The route given for that is factory_equipment_equipment_variations which is why I specified that in the url parameter, but I feel there must be a simpler way to acheive this. What should the form_with parameters look like?
Ok, so I figured this out. I was close, but the correct syntax is as follows
<%= form_with(model: [#factory, #equipment, EquipmentVariation.new], local: true) do |form| %>
So you need to give the model: parameter all the models for the route (factory, equipment) and it will figure out the correct path by itself and submit the parameters appropriately.
Also, my controller for EquipmentVariation called by above looks like:
def create
#equipment = Equipment.find(params[:equipment_id])
#equipment.variation_types << VariationType.find(params[:variation_type][:variation_type_id])
end
Hope this helps someone in future!

Sorting questions with nested resources using jquery

There are many questions already on this topic but I cannot seem to find anything that works. Using this railscast, I'm trying to sort a list of questions using jquery-ui but like this question my nested resources are confusing things.
I have three models: posts, comments and questions.
Post.rb:
class Post < ActiveRecord::Base
has_many :comments
has_many :questions, :through :comments
end
Comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
has_many :questions
end
Question.rb
class Question < ActiveRecord::Base
belongs_to :comment
end
The list of questions I'd like to sort is on the ordered_path view (posts/:id/ordered). Here is the posts controller:
Posts_controller.rb
def ordered
#post = Post.friendly.find(params[:id])
#ordered = #post.questions.where(:hide => true).where(:recommend => true).order("position")
end
and questions_controller.rb:
def sort
params[:question].each_with_index do |id, index|
Question.update_all({position: index+1}, {id: id})
end
render nothing: true
end
I believe I've followed the railscast correctly. I've added a 'position' column to questions. I added this to the routes:
routes.rb
resources :comments do
resources :questions do
collection { post :sort }
end
end
And in my view I have this
posts/ordered.html.erb
<ul id="questions" data-update-url="<%= sort_comment_questions_path %>">
<% #ordered.each do |question| %>
<%= content_tag_for :li, question do %>
<span class="handle">[drag]</span>
<%= question.body %>
<% end %>
<% end %>
</ul>
Lastly, posts.js.coffee:
jQuery ->
$('#questions').sortable
axis: 'y'
handle: '.handle'
update: ->
$.post($(this).data('update-url'), $(this).sortable('serialize'))
My problem(s) is that I am unsure what to pass into the data-update-url (to get rid of the 'no route matches' error) or if that's even the correct path in the first place.
First thing in your code, change the line
#ordered = #post.questions.where(:hide => true).where(:recommend => true).order("position")
to
#ordered = #post.questions.where(:hide => true, :recommend => true).order("position")
As you typically only want one where() call if you can help it. Sometimes you will need to add one conditionally which is fine. In an if block for example.
As far as your route error, run rake routes in terminal, and you will see output of all the route methods, the params they accept, the HTTP method, and what controller#action it hits.
The important thing to note about nested resources, is the nested resource is applied to a "member" of the parent. So in your case what your two resources blocks are generating are:
GET /comments/:comment_id/questions questions#index
GET /comments/:comment_id/questions/:id questions#show
POST /comments/:comment_id/questions/sort questions#sort
So in your erb tags in the data attribute, you need to add the comment to it:
<ul id="questions" data-update-url="<%= sort_comment_questions_path(#comment) %>">
The problem though is that you are using this at the post model level, which has many comments. So what you probably want is this:
resources :comments do
resources :questions
end
resources :posts do
member do
post "sort" => "questions#sort", :as => "sort_questions"
end
end
Then in your view:
<ul id="questions" data-update-url="<%= sort_questions_post_path(#post) %>">

rails relationship. Rails 4

I'm new in the world of rails developers. Please, help me to understand.
I've 3 tables:
Calls, Questions, Results
Calls is:
id, name, date
Questions is:
id, question
Results is:
id, call_id, question_id, result
I've read the Rails manual, as i understand i've created 3 models.
In my model Call.rb
I've done next relationship:
has_many :results
has_many :question, through: :results
My Result.rb
belongs_to :call
belongs_to :question
My Question.rb
has_many :result
So, there are can be many records in the table "results" with one call_id, and it's can be one relation with question through results table
If if try to launch code like this:
#calls = Call.all
Than on my view:
<% #calls.each do |call| %>
<%= call.result.result %>
<% end %>
i've error that "result is undefined method". But it's must be a property.
What i do wrong?
Thanks!
According to your schema, your associations should look like this
class Call < ActiveRecord::Base
has_many :questions
has_many :results
end
class Question < ActiveRecord::Base
belongs_to :call
end
class Result < ActiveRecord::Base
belongs_to :call
end
So in the view,
<% #calls.each do |call| %>
<% call.results.each do |result| %>
<%= result.result%>
<% end %>
<% end %>
A few things.
First, you need to fix your associations so the plural and singular tenses match. has_many :result does not work as Marcelo points out.
Second, you need to ensure that your tables actually have the correct id's to make the associations work. Use the rails console to inspect Result. From your question info, it should have attributes for call_id and question_id. Once you've confirmed this, create a few objects in the console and test your associations.
#call = Call.create(name: "test", date: Time.now)
#result = Result.create(call_id: #call.id, result: "some result")
Then
#call.result # should yield the Result record you just created
Lastly, you need to rename the result attribute for Result. That's super confusing and will only cause problems.
The first thing I notice is that your call should have many questions, and many results through questions. That's because calls own questions, which in turn own results themselves.
class Call < ActiveRecord::Base
has_many :questions
has_many :results, through: :questions
end
You didn't need call_id in Result class. But, if you wish to keep it there, you dont need through: :questions in your call class (given there is a direct relation between them)
In your Question class, I assume it is a typo, but it should be plural
has_many :results
Having said that, your loop through calls will bring results (plural) and not result (singular) given that a call may have many results. Therefore:
<% #calls.each do |call| %>
<% call.results.each do |result| %>
<%= call.result %>
<% end %>
<% end %>

Rails active record associations, nested models

Been going through http://guides.rubyonrails.org/association_basics.html but can't seem to get my head around this
I have 4 models: users, listings, comments, commentresponses. Somebody creates a listing, someone else can comment on the listing, then the original creator can respond to the comment.
class User < ActiveRecord::Base
has_many :comments, foreign_key: 'provider'
has_many :listings
has_many :comments
has_many :commentresponses
end
class Listing < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :listing
belongs_to :user
has_one :commentresponse
end
class Commentresponse < ActiveRecord::Base
belongs_to :comment
belongs_to :user
end
Everything is working well except I can't access comment.commentresponse; this give a no method error.
Any recommendations of where my logic is wrong?
Associations
I wouldn't use a separate model for CommentResponse; keep it all in the Comment model - using a gem such as ancestry to give a parent / child system to the different comments:
Above is an example of one our Category models - showing how you can order the different associations with the likes of the ancestry gem. The reason why I posted is because this is how you can create responses to your comments, rather than having a separate model:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :listings
has_many :comments
end
#app/models/listing.rb
class Listing < ActiveRecord::Base
belongs_to :user
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :listing
belongs_to :user
has_ancestry #-> make sure you have "ancestry" column with string in db
end
This basically allows you to use the various methods which ancestry appends to your objects:
Ancestry
I would recommend using the Ancestry gem to store the responses of the comments. You can then add to this by using several partials to provide a nested interface. This way, it will show you the comments you want, with the correct responses etc
IMPORTANT
When using ancestry - you define the parents of the rows with comment_1/comment_2. Many people think you have to just define the "parent"; not true. You have to define the entire "history" of the ancestors of an object
--
Tree
If you go with the ancestry approach, you'll be able to do something like the following:
To achieve this, you can use the nested partial we created here (obviously replace for use with comments):
#app/views/categories/index.html.erb
<%= render partial: "category", locals: { collection: #categories } %>
#app/views/categories/_category.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
</div>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
I know it's not a direct answer to your question; it certainly should help you though

Rails associations nesting

I have three tables that are created by a non rails application. The following models are just the associations for the tables.
class Client < ActiveRecord::Base
has_many :programs
has_many :locations, :through => :programs
end
class Program < ActiveRecord::Base
belongs_to :location
belongs_to :client
end
class Location < ActiveRecord::Base
has_many :programs
has_many :clients, :through => :programs
end
I would like a simpler way to list out all the programs by location for a client.
I thought the following code would work, but it returns all programs for that location, and that completely makes sense.
#client.locations.each do |location|
<h2>location.name</h2>
<ul>
location.programs.each do |program|
<li>program.name</li>
end
</ul>
end
I'm currently using
#client.locations.each do |location|
<h2>location.name</h2>
<ul>
Program.where(:location_id => location.id, :client_id => #client.id).each do |program|
<li>program.name</li>
end
</ul>
end
But this is against MVC principals and is ugly.
I'm not sure how to write this code elegantly, and would appreciate input.
You can use includes method for eager loading the data. Also, using include for this situation will create a join query that will fetch only those programs which are related to both location and client.
#client.locations.includes(:programs).each do |location|
<h2>location.name</h2>
<ul>
location.programs.each do |program|
<li>program.name</li>
end
</ul>
end
Though you should avoid using database queries in view files. So you can move #locations = #client.locations.includes(:programs) to your controller action and use #locations in view to maintain the structure.
You can do this
#client.programs.each do |program|
<h2>program.location.name</h2>
<ul>
<li>program.name</li>
end
</ul>
end

Resources