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
Related
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!
I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end
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 %>
I'm building a guestlist app and I have defined both Guest (name) and List models - guests can have many lists and lists can have many guests. Both are associated in a has_many through association (after reading that HABTM associations aren't a good idea).
Here are my models:
class Guest < ActiveRecord::Base
has_many :lists, through: :checklists
end
class List < ActiveRecord::Base
has_many :guests, through: :checklists
end
class Checklist < ActiveRecord::Base
belongs_to :list
belongs_to :guest
end
EDIT - my lists controller for show:
def show
#list = List.find(params[:id])
end
On the List show view, I want to display the all of the guest names that are tied to that list through the checklist table. I can figure out if I need a do loop or an array...this is a bit beyond my current skill.
I've tried things like the following:
<%= #list.checklist.guest.name %>
I'm clearly missing some key bit of code and concept here.
Thanks in advance.
You need to iterate over guests like this:
<% #list.guests.each do |guest| %> # For each guest in list.guests
<%= guest.name %> # print guest.name
<% end %>
It should be something like this
<% #list.guests.each do |guest| %>
<%= guest.name %>
<% end %>
Using Rails 3.0.10 & Ruby 1.9.2.
I have a Book model which has many Pages.
class Book < ActiveRecord::Base
has_many :pages
# is this right?
def pages
Page.order('page_number asc').find_all_by_book_id(:id)
end
end
class Page < ActiveRecord::Base
belongs_to :book
end
When I retrieve the pages of a book, I always want them ordered by their page number. What is the proper way to handle this so that calling book.pages will return all pages in sequence?
When editing the book I also show the content of each page which can be edited. If I am using nested attributes would this return the pages in their proper sequence, assuming my previous question is resolved?
<%= form_for [#book, #pages] do |f| %>
<%= f.fields_for :pages do |page| %>
do something
<% end %>
<% end %>
Thanks
I believe you could actually specify the default order directly on the association declaration:
class Book < ActiveRecord::Base
has_many :pages, :order => "page_number asc"
end
class Page < ActiveRecord::Base
belongs_to :book
end
This way, you won't have to specify the Book#pages method yourself, and it will still be ordered by page_number asc by default.
for newer versions of Rails, the syntax has changed:
class Book < ActiveRecord::Base
has_many :pages, -> { order "page_number asc" }
end
class Page < ActiveRecord::Base
belongs_to :book
end