Only show replies realted to post - ruby-on-rails

Currently I'm looping to show each post and inside that loop looping to show all related replies. However currently all replies are showing up under every post on every board.
How can I only show the replies that are related to each post?
resources :boards, :path => '' do
resources :posts, :path => 'thread' do
resources :replies
class BoardsController < ApplicationController
def show
#board = Board.friendly.find(params[:id])
#boards = Board.all
#replies = Reply.all
end
class PostsController < ApplicationController
def show
#board = Board.friendly.find(params[:board_id])
#boards = Board.all
#replies = Reply.all
#post = Post.includes(:board).where('boards.slug' => params[:board_id]).friendly.find(params[:id])
end
class Board < ActiveRecord::Base
has_many :posts
has_many :replies, through: :posts
include FriendlyId
friendly_id :name, use: :slugged
accepts_nested_attributes_for :posts, :replies
end
class Post < ActiveRecord::Base
belongs_to :board
has_many :replies, dependent: :destroy
accepts_nested_attributes_for :replies
include FriendlyId
friendly_id :pid, :use => :scoped, :scope => :id
end
views/boards/show:
<% #board.posts.find_each do |post| %>
<%= post.subject %>
<%= post.name %>
<%= post.email %>
<%= post.created_at %>
No.<%= post.pid %>
<%= link_to "[reply]", board_posts_path(#board, #post)%>
<br>
<%= post.comment %><br><br>
<% render "replies/replies" %>
<% end %>
views/replies/_replies:
<% #replies.each do |reply| %>
<p>
>><%= reply.name %>
<% reply.created_at %>
<%= reply.email %>
<%= reply.subject %>
<%= reply.created_at.strftime("%m/%d/%Y(%a) %I:%M:%S ") %>
No.<%= reply.pid %>
<br>
<%= reply.comment %>
</p>
<% end %>

you don't need to set replies at controller level as replies belongs to posts. you can get all the replies for a given post by post.replies
in you view
#posts.each do |post|
replies = post.replies // get all the replies that belongs to this post
replies.each do |reply|
reply.attribute
end
end

Since you want to show only related replies in _replies, do not use generic #replies, use post there:
views/boards/show:
...
<% render "replies/replies", locals: {post: post} %>
views/replies/_replies:
<% post.replies.each do |reply| %>
...

Related

Ruby on Rails Nested Models not Updating with form_for, but no error on update

So in my rails project, I have a Patient class, which has one Treatment class. This treatment class then has many DrNotes inside of it. I am still fairly new to rails, and I am aware that nesting this deeply is not recommended in Rails, but I am proceeding with this method.
My problem is with the editing of DrNotes. Since there are many doctor notes within treatment, I am trying to only edit one specific note. I am using Form_for to pass parameters to the doctor's note. When I submit the form, it redirects me to the page that should be shown only when the update function has succeeded. However, none of the notes are actually updated, and no errors are thrown when I try to perform the update.
Here are the models in question:
patient.rb
class Patient < ApplicationRecord
has_one :treatment, dependent: :destroy
accepts_nested_attributes_for :treatment, update_only: true
end
treatment.rb
class Treatment < ApplicationRecord
belongs_to :patient
has_many :dr_notes, class_name: "DrNote",
foreign_key: "treatment_id", dependent: :destroy
accepts_nested_attributes_for :dr_notes
end
dr_note.rb
class DrNote < ApplicationRecord
belongs_to :treatment
end
In my controller I have:
Doctor Note Edit Function
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
Doctor Note Update Function
def update_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
if #dr_note.update(dr_note_params)
redirect_to page_path(#patient)
else
flash.now[:error] = "Cannot update Doctor's notes"
render 'edit_dr_note'
end
end
Doctor Note Params
def dr_note_params
params.require(:dr_note).permit(:id, :name, :message)
end
I have :id in the params.permit because from researching, I heard that you need to include it when updating models, but i'm not sure if it is needed here.
I have the following code in the routes.rb
get '/pages/:patient_id/treatment/edit/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/update/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And in the edit_dr_note.html.erb
<%= form_for #patient.treatment.dr_notes.find(params[:dr_id]), url: update_dr_note_path do |patient_form| %>
<% #patient.treatment.dr_notes.each do |doctor| %>
<% if doctor.id == #dr_note.id %> #Only displays the fields for the desired note
<%= patient_form.fields_for :dr_note, doctor do |doctor_fields| %>
Name: <%= doctor_fields.text_field :name %>
Message: <%= doctor_fields.text_field :message %>
<% end %>
<p>
<%= patient_form.submit %>
</p>
<% end %>
<% end %>
<% end %>
Any help would be greatly appreciated. Thanks!
You are mixing two approaches(the nested resources and the nested attributes). Use one to serve your purpose.
With the nested resources:
<%= form_for [:pages, #patient, #treatment, #dr_note], url: update_dr_note_path do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_note.text_field :message %>
<p>
<%= dr_note.submit %>
</p>
<% end %>
The routes would be
get '/pages/:patient_id/treatment/:treatment_id/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/:treatment_id/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
Edit the edit_dr_note to define #treatment
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#treatment = #patient.treatment
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
And finally remove accepts_nested_attribute_for from the models, you don't need it in this approach.
With the nested attributes:
Keep the accepts_nested_attributes_for in the models. And change the routes and form like below
get '/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And the form_for
<%= form_for #patient, url: update_dr_note_path do |patient| %>
<%= patient.fields_for :treatment do |t| %>
<%= t.fields_for :dr_notes, #dr_note do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_notetext_field :message %>
<% end %>
<% end %>
<p>
<%= patient.submit %>
</p>
<% end %>
And change the dr_note_params method as below
def dr_note_params
params.require(:patient).permit(:id, treatment_attributes: [:id, dr_notes_attributes: [:id, :name, :message])
end
When you write the following line, you're trying to find a DrNote using the dr_id:
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
Whereas the dr_notes relation on Treatment does not seem to define any particular behavior, and this is your problem.
You'll need to find_by doctor's id (or dr_id in your code) and thus first define the relation on DrNote.

form_for a "belongs_to" model in 'show' of parent model

I have one model "Breads" that has_many "Posts".
I would like to have a form to create a new "Post" on the 'show' page for a given "Bread" that creates the association to the record of 'Bread' which the 'show' page is displaying.
I have tried a few different methods, but all are giving an error. The method that I have shown below gives a "Association cannot be used in forms not associated with an object" error.
/views/breads/show.html.erb:
<p>
<strong>Bread Type:</strong>
<%= #bread.bread_type %>
</p>
<table>
<tr>
<th>Uploaded By</th>
<th>Comment</th>
<th>Picture</th>
</tr>
<% #bread.posts.each do |post| %>
<tr>
<td><%= post.uploader %></td>
<td><%= post.comment %></td>
<td><%= image_tag post.attachment_url.to_s %></td>
</tr>
<% end %>
</table>
<%= #bread.id %>
<%= simple_form_for #bread do |b| %>
<%= simple_fields_for :posts do |p| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.association :bread, value: #bread.id %>
<%= p.file_field :attachment %><br>
<%= p.button :submit %>
<% end %>
<% end %>
<%= link_to 'Back', breads_path %>
config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'
root 'welcome#index'
resources :breads
resources :posts
end
controllers/breads_controller.rb:
class BreadsController < ApplicationController
def index
#breads = Bread.all
end
def show
#bread = Bread.find(params[:id])
end
def new
#bread = Bread.new
end
def edit
#bread = Bread.find(params[:id])
end
def create
#bread = Bread.new(bread_params)
if #bread.save
redirect_to #bread
else
render 'new'
end
end
def update
#bread = Bread.find(params[:id])
if #bread.update(bread_params)
redirect_to #bread
else
render 'edit'
end
end
def destroy
#bread = Bread.find(params[:id])
#bread.destroy
redirect_to breads_path
end
private
def bread_params
params.require(:bread).permit(:bread_type)
end
end
models/bread.rb:
class Bread < ActiveRecord::Base
has_many :posts
validates :bread_type, presence: true, uniqueness: true
end
models/post.rb:
class Post < ActiveRecord::Base
belongs_to :bread
mount_uploader :attachment, AttachmentUploader
end
Do this -
<%= simple_form_for #bread do |b| %>
<%= b.simple_fields_for(:posts,#bread.posts.build) do |p| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.file_field :attachment %><br>
<%= p.button :submit %>
<% end %>
<% end %>
and make changes in beard_params
def beard_params
params.require(:bread).permit!
end
Here permit! requires all parameters and for other way you can use #pawan's answer.
Extending #Amit Suroliya answer, you need to add posts_attributes to bread_params
def bread_params
params.require(:bread).permit(:id, :bread_type, posts_attributes: [:id, :uploader, :comment, :bread_id, :attachment])
end
Update:
You also need to add accepts_nested_attributes_for :posts in Bread model.
Iam sorry, but this is not good way at all, try to don't abuse rails and rest routes :)
Here is easy example how to do that:
config/routes.rb
resources :bread do
resources :posts
end
This means there will be routes like:
bin/rake routes
breads - breads#index
bread/:id - breads#show
etc..
and most important
bread/:bread_id/posts/:id
...
That means posts are nested resources for bread...
app/controllers/breads_controller.rb
controller BreadsController < BaseController
before_action :find_bread, except: %i(index create new)
.... action new, update, edit etc..
end
but now its the important part in PostsController..
app/controllers/posts_controller.rb
controller PostsController < BaseController
before_action :find_bread
before_action :find_post, except: %i(index new create)
before_action :build_post, only: %i(new create)
.... action new, update, edit etc..
# Example with :return link
def create
if #post.save
if params[:back] == 'bread_show'
redirect_to bread_path(#bread)
else
redirect_to bread_post_path(#bread, #post)
end
else
render 'new'
end
end
private
def build_post
if params[:post]
#post = #bread.posts.build(post_params)
else
#post = #bread.posts.build
end
end
def find_post
#post = #bread.posts.find(params[:id])
end
def find_bread
#bread = Bread.find(params[:bread_id])
end
... post params ...
end
Now you have rest full routes and you're able to do what you want without such a pain and clean
... output hidden
<%= #bread.id %>
<%= simple_form_for #bread.posts.build do |b| %>
<%= p.input :uploader %>
<%= p.input :comment %>
<%= p.file_field :attachment %><br>
<%# Send back link to return on proper page %>
<%= p.hidden_field :back, 'bread_show' %>
<%= p.button :submit %>
<% end %>
<%= link_to 'Back', breads_path %>
There can be some mistakes, I write this code from memory, can't try that :(

Deleting a HABTM object

I have the following code which gives me an ActionView::Template::Error (undefined methodroster_path' for #<#:0x007fe34005c208>):`
While in the background it deletes the association player_roster (Has and belongs to many), but i want to delete it when i press the link.
The roster path is nested within a team, but the issue is regarding roster and players.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player.rosters.delete(#roster) %>
<% end %>
<% end %>
:Update
Player.rb
class Player < ActiveRecord::Base
has_and_belongs_to_many :rosters
belongs_to :country
mount_uploader :picture, PictureUploader
end
Roster.rb
class Roster < ActiveRecord::Base
has_and_belongs_to_many :players
has_many :placements
belongs_to :team, touch: true
end
The way you are doing it now will call your delete when the page loads. You can't link to arbitrary Ruby code, you need to link to a route and controller action which will perform your logic.
<%= form_for [#team, #roster] do |f| %>
<% #players.each do |player| %>
<%= player.gamertag %>
<%= link_to "Delete", player_roster_path(player, #roster), method: :delete %>
<% end %>
<% end %>
This link will route to players/:id/rosters/:id with the DELETE HTTP action, which Rails will route to the destroy method.
class RostersController < ApplicationController
def destroy
#player = Player.find(params[:player_id])
#roster = Roster.find(params[:id])
#player.rosters.destroy(#roster)
# redirect/render
end
end
You also will need to setup player_roster_path as a route in config/routes.rb
resources :players do
resources :rosters, only: [:destroy] # you may have other routes here as well
end

Accepts nested attributes for multiple records

I have two resources: Recommendations and Ratings.
Recommendations Model:
class Recommendation < ActiveRecord::Base
has_many :ratings
accepts_nested_attributes_for :ratings
end
Ratings Model:
class Rating < ActiveRecord::Base
belongs_to :recommendation
end
Recommendations Controller:
class RecommendationsController < ApplicationController
def index
#product = Product.find(params["product_id"])
#recommendations = Recommendation.find(:all, :joins => :products, :conditions => ["product_id = ?", (params["product_id"])])
#recommendation = Recommendation.new
#rating = Rating.new
end
def create
#need to find_or_create new rating based on recommendation_id
end
end
Recommendations Index:
<div id ="prods_container">
<%= form_for #recommendation do |f| %>
<%= f.fields_for #rating do |r| %>
<% #recommendations.each do |rec| %>
<%= rec.title %>
<div id="rec_note_text"><%= r.text_field :notes %></div>
<% end %>
<%= f.submit %>
<% end %>
<% end %>
</div>
I am trying to add a notes field for each of the "recommendation" objects which I need to update the ratings table in my create action/recommendations controller. How do I pass the correct ID's to my controller to achieve this?

How to add has_many :through details to a form RoR

I'm doing what appears to be a common learning app for Ruby on Rails, the recipe app. Specifically, working on recipes and ingredients as a has_many :through relationship. Through looking at a million examples and questions, I've got my many-to-many relationship setup and my multi-model form working, but I'd like to add an additional field and can't get it working. Feels like I'm close to understanding how this stuff works. Here are the quick details:
Models:
class Ingredient < ActiveRecord::Base
has_many :recipe_ingredients
has_many :recipes, :through => :recipe_ingredients
end
class RecipeIngredient < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
has_many :ingredients, :through => :recipe_ingredients
accepts_nested_attributes_for :ingredients, :recipe_ingredients
def new_recipe_ingredient_attributes=(recipe_ingredient_attributes)
recipe_ingredient_attributes.each do |attributes|
recipe_ingredients.build(attributes)
end
end
def existing_recipe_ingredient_attributes=(recipe_ingredient_attributes)
recipe_ingredients.reject(&:new_record?).each do |recipe_ingredient|
attributes = recipe_ingredient_attributes[recipe_ingredient.id.to_s]
if attributes
recipe_ingredient.attributes = attributes
else
recipe_ingredient.delete(recipe_ingredient)
end
end
end
def save_recipe_ingredients
recipe_ingredients.each do |recipe_ingredient|
recipe_ingredient.save(false)
end
end
end
Controller:
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to :action => 'show', :id => #recipe
flash[:notice] = "Your record has been saved."
else
render :action => 'new'
end
end
def update
params[:recipe][:existing_recipe_ingredient_attributes] ||= {}
#recipe = Recipe.find(params[:id])
if #recipe.update_attributes(params[:recipe])
redirect_to :action => 'show', :id => #recipe
flash[:notice] = "Your changes have been saved."
else
render :action => 'edit'
end
end
View:
<% form_for(#recipe) do |f| %>
<%= f.label :name %><br />
<%= f.text_field :name %>
etc.....
Ingredients:
<div id="recipe_ingredients">
<div class="recipe_ingredient">
<% new_or_existing = recipe_ingredient.new_record? ? 'new' : 'existing' %>
<% prefix = "recipe[#{new_or_existing}_recipe_ingredient_attributes][]" %>
<% fields_for prefix, recipe_ingredient do |ri_form| %>
<p>
<%= ri_form.collection_select(:id, Ingredient.find(:all), :id, :name, :include_blank => true) %>
<%= ri_form.text_field :amount %>
</p>
<% end %>
</div>
</div>
</p>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
Sorry for the wall of code, hopefully it makes sense. The thing I can't understand is why the "amount" text field doesn't work. I've tried a million different ways, but can't get it working. In this case, the error I get is "undefined method `amount' for #"
What key connection am I missing here? Thanks.
At first glance it appears you should simply replace:
<% fields_for prefix, recipe_ingredient do |ri_form| %>
with:
<%= fields_for prefix, recipe_ingredient do |ri_form| %>

Resources