How to set up nested resources controller in Rails 4? - ruby-on-rails

I have 3 models in my application. User (via Devise), post, and reply.
User can be parent of both posts and replies, and reply belongs to users and posts.
For some reason it doesn't save replies to database. On output i can not see user_id. The function i used to create #reply is current_user.replies.create(reply_params). Also html outputs braces seem bit odd to me. Why is only :comment in :replys braces? Any idea what i did wrong?
Models:
class Post < ActiveRecord::Base
belongs_to :user
has_many :replies
validates_presence_of :title, :tekst
validates :tekst, length: {minimum: 10}
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :posts, dependent: :destroy
has_many :replies
end
class Reply < ActiveRecord::Base
belongs_to :post
belongs_to :user
validates_presence_of :user, :post, :comment
validates :comment, length: {minimum: 10}
end
Controller:
class RepliesController < ApplicationController
before_action :set_post, only: [:new, :index, :create]
before_filter :authenticate_user!
def index
#replies = #post.replies
end
def new
#reply = Reply.new
end
def create
#reply = current_user.replies.create(reply_params)
respond_to do |format|
if #reply.save
format.html { redirect_to #post, notice: 'Reply was
successfully saved' }
format.json { render json: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: 'Wrong entry'
}
end
end
end
private
def reply_params
params.require(:reply).permit(:comment, :post_id)
end
def set_post
#post = Post.find(params[:post_id])
end
end
Form:
<%= form_for [#post, #reply] do |f| %>
<%= f.text_field :comment %>
<% f.hidden_field :post_id, value: #post.id %>
<%= f.submit %>
<% end %>
Schema:
create_table "replies", force: :cascade do |t|
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "comment"
end
add_index "replies", ["post_id"], name: "index_replies_on_post_id"
add_index "replies", ["user_id"], name: "index_replies_on_user_id"
And how it looks at output:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"lot of nubers",
"reply"=>{"comment"=>"sdfsddasd"}, "commit"=>"Create Reply",
"post_id"=>"1"}

It's failing because you are not associating a post with the reply. Try this...
def create
#reply = #post.replies.new(reply_params)
#reply.user = current_user
respond_to do |format|
if #reply.save
...
end
end
Also, notice that you should instantiate the reply (with new, instead of create), then save it.

Related

Method create and destroy doesn't work : ruby on rails

I am beginner on coding and on Ruby on rails.
I start to code an app to reference my favorite restaurants.
I start coding my action on my Restaurant Controller and the action Index, Show and Update work.
But I don't understand why my method create and destroy don't work.
Data base : schema.rb :
create_table "restaurants", force: :cascade do |t|
t.string "name"
t.string "price"
t.string "address"
t.string "category"
t.string "website"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.index ["user_id"], name: "index_restaurants_on_user_id"
end
Model : restaurant.rb :
class Restaurant < ApplicationRecord
belongs_to :user
CATEGORY = ['Gourmand', 'Healthy', 'Asiatique', 'Italien', 'Cuisine du monde', 'Traditionel',
'Festive', 'Chic', 'Populaire', 'Fast-food', 'Casher', 'Halal', 'Vegan/Végétarien']
PRICE = ['€', '€€', '€€€', '€€€€']
validates :name, presence: true
validates :address, presence: true
validates :price, inclusion: { in: Restaurant::PRICE }, presence: true
validates :category, inclusion: { in: Restaurant::CATEGORY }, presence: true
end
Controller : restaurants_controller.rb
class RestaurantsController < ApplicationController
skip_before_action :authenticate_user!
before_action :set_restaurant, only: [:show, :edit, :update, :destroy]
def index
#restaurant = Restaurant.all
end
def show
end
def new
#restaurant = Restaurant.new
end
def create
#restaurant = Restaurant.new(restaurant_params)
#restaurant.save!
redirect_to restaurants_path(#restaurant), notice: 'new add'
# if #restaurant.save
# redirect_to restaurants_path(#restaurant), notice: 'New restaurant added'
# else
# render :new, status: :unprocessable_entity
# end
end
def edit
end
def update
#restaurant.update(restaurant_params)
redirect_to restaurant_path(#restaurant), notice: 'Restaurant was successfully updated.'
end
def destroy
#restaurant.destroy
redirect_to restaurants_path, status: :see_other
end
private
def restaurant_params
params.require(:restaurant).permit(
:name,
:address,
:category,
:website,
:price
)
end
def set_restaurant
#restaurant = Restaurant.find(params[:id])
end
end
View new : new.html.erb :
<h1>Add a new restaurant </h1>
<%= render 'restaurants/form', restaurant: #restaurant %>
View simple form : _form.html.erb
<%= simple_form_for(restaurant) do |f| %>
<%= f.input :name %>
<%= f.input :address %>
<%= f.input :category, collection: Restaurant::CATEGORY%>
<%= f.input :price, collection:Restaurant::PRICE %>
<%= f.input :website%>
<%= f.submit "#{ restaurant.persisted? ? 'Update' : 'Create' } Restaurant" %>
<%#= link_to 'Back to restaurant', restaurants_path %>
<% end %>
I add Devise Gems by the way but I skip the authentification with a :
skip_before_action :authenticate_user!
on my Restaurant Controller so it does not come from Devise.
Thank you :)
I check on my rails console : no restaurants created
When I put a 'raise' in the action 'create' just before my #restaurant.save, I can see on the params all the informations I added on my form.
But I don't know why it doesn't save my information and don't create the restaurant.

Adding Comments Section To Posts In Rails

I am trying to add a comments section to posts on my web applications but getting an error when saving the comment
I have been following a tutorial to add a comments section to my posts and have been modifying as I go to work with my app. I am still relatively new to rails and I am still learning. I understand what the error message is telling me, but I am unsure as to how to proceed
Comments Controller:
class CommentsController < ApplicationController
def create
#micropost = Micropost.find_by(id: params[:id])
#comment =
#micropost.comments.create(params[:comment].permit(:body))
end
end
Microposts Contoller:
class MicropostsController < ApplicationController
before_action :logged_in_user, :upvote, :downvote, only:
[:create, :destroy]
before_action :correct_user, :upvote, :downvote, only:
:destroy, allow_destroy: true
def create
#micropost = current_user.microposts.build(micropost_params)
#maximum_length = Micropost.validators_on( :content,
:headline).first.options[:maximum]
if #micropost.save
flash[:success] = "Article Posted"
redirect_to root_url
else
#feed_items = []
render 'articles/home'
end
end
def destroy
#micropost.destroy
flash[:success] = "Micropost deleted"
redirect_to request.referrer || current_user
end
def show
#micropost = Micropost.find(params[:id])
end
private
def micropost_params
params.require(:micropost).permit(:content, :headline)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
Comments Form being rendered on post:
<%= form_for([#micropost, #micropost.comments.build]) do |f| %>
<br>
<p>
<%= current_user.name %>
<%= f.text_area :body %>
</p>
<br>
<p>
<%= f.submit %>
</p>
<% end %>
Comments Model:
class Comment < ApplicationRecord
belongs_to :micropost
end
Microposts Model:
class Micropost < ApplicationRecord
acts_as_votable
has_many :comments
belongs_to :user
validates :user_id, presence: true
validates :headline, presence: true, length: { maximum: 200 }
validates :content, presence: true, length: { maximum: 5000 }
end
Tables:
create_table "comments", force: :cascade do |t|
t.string "name"
t.text "body"
t.bigint "microposts_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "micropost_id"
t.index ["microposts_id"], name:
"index_comments_on_microposts_id"
end
create_table "microposts", force: :cascade do |t|
t.text "content"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "headline"
t.index ["user_id", "created_at"], name:
"index_microposts_on_user_id_and_created_at"
t.index ["user_id"], name: "index_microposts_on_user_id"
end
The form is being rendered on the micropost show view. I can see the form fine.
When I press to save the comment on the post I get an error stating undefined method comments for nil:NilClass and it highlights the Comments controller and the line #comment = #micropost.comments.create(params[:comment].permit(:body))
I know there probably should a method somewhere for comments. The tutorial I watched didn't add anything like that. So I unsure do I need to modify some existing code, or do I need to add a method somewhere called comments?
where is the form to create comments, you are not sending the parameter params [: id], and with this parameter you are looking for the # micropost .. you must send the id in that form, a quick solution could be
<% = f.hidden_field: id, value: # micropost.id%>
 
in the comment creation form
Okay so the answers you all gave pointed me in the right direction. I wasn't actually passing an proper id. Instead of #micropost = Micropost.find_by(id: params[:id]) it should have been #micropost = Micropost.find_by(id: params[:micropost_id])
Many thanks all for getting me there.

has_many through same table Rails

I want to create a Rails app that allows "users" to follow other users. I am semi-new to more complex relationships and am attempting to set up has_many through for the first time. I want friends to be able to follow other users.
Here is my join table:
class Following < ApplicationRecord
belongs_to :user
belongs_to :follower, class_name: "User"
end
Here is my users table:
class User < ApplicationRecord
has_many :followings
has_many :followers, through: :followings
end
Here is my schema:
create_table "followings", force: :cascade do |t|
t.integer "user_id"
t.integer "follower_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I don't know how to set up a form to actually create the relationship. In a users view, I have this, but it doesn't work.
<%= form_for #following do |f| %>
<%= f.hidden_field :follower_id, :value => #user %>
<%= f.select :user_id, #users.collect { |u| [u.name, u.id] } %>
<%= f.submit %>
<% end %>
As I said, I am very new to this type of relationship. I need help. I don't know how to link records through a form.
I am following this tutorial: https://teamtreehouse.com/library/what-is-a-hasmany-through-association-in-ruby-on-rails
I am assuming you have a current_user method that returns the logged in user - like what Devise provides. If not you need to setup authentication first.
Create a nested route:
# config/routes.rb
resources :users, only: [] do
resources :followings, only: [:create, :destroy], shallow: true
end
Add a validation to Following to avoid duplicates:
class Following < ApplicationRecord
belongs_to :user
belongs_to :follower, class_name: "User"
validates_uniqueness_of :user_id, scope: 'follower_id'
end
Add a utility method to User to see if he is following another user:
class User < ApplicationRecord
has_many :followings
has_many :followers, through: :followings
def following?(user)
followings.exist?(user: user)
end
def find_following(user)
followings.find_by(user: user)
end
end
We can then add Follow and Unfollow buttons (they are actually forms) to the /users/show.html.erb view.
<% if current_user.following?(#user) %>
<%= button_to "Unfollow", current_user.find_following(#user), method: :delete %>
<% else %>
<%= button_to "Follow", [#user, #user.followings.new] %>
<% end %>
Note that we don't need any form params since we are using a nested route (POST /users/:user_id/followings) to pass the user id (who gets followed) and we are getting the current user from the session.
We can then setup our controller:
class FollowingsController < ApplicationController
# POST /users/:user_id/followings
def create
#user = User.find(params[:user_id])
#following = Following.new(user: #user, follower: current_user)
if #following.save
redirect_to #user, success: "You are now following #{ #user.name }"
else
redirect_to #user, error: "Could not create following"
end
end
# DELETE /followings/:id
def destroy
#following = Following.find(params[:id])
#following.destroy
redirect_to #following.user, success: "You are no longer following #{ #user.name }"
end
end

How do I save my child records from the parent controller?

I have a bunch of 'kid' objects saved already and I want to create a parent object which is linked to the kids via a 'relative' model.
This object gives me a many-to-many, through relatives.
To be clear: the user visits the 'parents' page, clicks create parents and is presented with a form that lets them name the parent and add up to four children to this parent (by creating 'relatives'), each of these 'relations' is also named - that's an important part. So, I could name the relation 'step son' or 'son', for instance.
Here's the code I have so far:
class Kid < ActiveRecord::Base
has_many :relatives
has_many :parents, through: :relatives
end
class Parent < ActiveRecord::Base
has_many :relatives
has_many :kids, through: :relatives
accepts_nested_attributes_for :relatives,
:reject_if => lambda { |a| a[:content].blank? },
:allow_destroy => true
end
class Relative < ActiveRecord::Base
belongs_to :parent
belongs_to :kid
end
class ParentsController < ApplicationController
before_action :set_parent, only: [:show, :edit, :update, :destroy]
before_action :lookup_kids, only: [:new, :edit]
# GET /parents
# GET /parents.json
def index
#parents = Parent.all
end
# GET /parents/1
# GET /parents/1.json
def show
end
# GET /parents/new
def new
#parent = Parent.new
4.times { #parent.relatives.build }
end
# GET /parents/1/edit
def edit
end
# POST /parents
# POST /parents.json
def create
#parent = Parent.new(parent_params)
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
respond_to do |format|
if #parent.save
format.html { redirect_to #parent, notice: 'Parent was successfully created.' }
format.json { render :show, status: :created, location: #parent }
else
format.html { render :new }
format.json { render json: #parent.errors, status: :unprocessable_entity }
end
end
end
# cut for brevity.
private
# Use callbacks to share common setup or constraints between actions.
def set_parent
#parent = Parent.find(params[:id])
end
def parent_params
params.require(:parent).permit(:name,
relatives_attributes: [:parent_id, :kid_id, :relationship, :_destroy])
end
def lookup_kids
#kids = Kid.all #for this nursery.
end
end
<%= form_for(#parent) do |f| %>
<% if #parent.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#parent.errors.count, "error") %> prohibited this parent from being saved:</h2>
<ul>
<% #parent.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<h4>Kids:</h4>
<%= f.fields_for :relatives do |r| %>
<%= r.label :kid %>
<%= r.collection_select :kid_id,
#kids, :id, :name, include_blank: true%>
<%= r.label :relationship %>
<%= r.text_field :relationship %>
<%= r.check_box :_destroy %>
<%= r.label :_destroy, "Remove" %>
<br/>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
ActiveRecord::Schema.define(version: 20151030113634) do
create_table "kids", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "parents", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "relatives", force: :cascade do |t|
t.string "relationship"
t.integer "parent_id"
t.integer "kid_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "relatives", ["kid_id"], name: "index_relatives_on_kid_id"
add_index "relatives", ["parent_id"], name: "index_relatives_on_parent_id"
end
When I get to 'create' in the parents controller, I can see the right parameters are getting through but the relationship records aren't being saved. SHouldn't this happen automatically?
I've tried looping through the :relatives_attributes but that doesn't seem to work with 'build'.
How am I suppsed to get the 'relatives' records to save?
EDIT: adding parameters posted:
parent"=>{
"name"=>"Dad",
"relatives_attributes"=>{
"0"=>{"kid_id"=>"2", "relationship"=>"Son", "_destroy"=>"0"},
"1"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"2"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"3"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"}}}
Edit: I've updated this to show my latest edit - note the 'parent_params[:relatives_attributes].each do |k,r|' in the controller. This now saves the kid records but the only problem is, it also saves the fields that are blank! So I have 'relative' records with null values for kid records. How can I stop it saving empty fields (or creating empty relative records)?
The answer was to build each sub-record of relative, like so:
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
Before calling #parent.save.
However, I'm still having issues getting rid of the blank records. So if anyone has an answer to that problem, please comment here - or if there's a better or more traditional way of doing this, hit me up. Follow up question here: Why is this reject_if in my model not rejecting blank records?
You are almost there, depending upon how your form submission is, you most likely need an accepts_nested_attribute_for in your Relative associative class as well:
class Relative
belongs_to :parent
accepts_nested_attributes_for :parent
belongs_to :kid
accepts_nested_attributes_for :kid
end
If this doesn't work, then please submit your params that are passed into the controller and we can adjust accordingly

user , comment association not working, comment.user.email returns No method error?

I have the following models with associations as given below:-
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :posts
has_many :comments
end
But when I try to access's comment's user details I GET NO METHOD ERROR :(.
The error displayed in browser is as below:-
undefined method `email' for nil:NilClass
1: <p>
2: <% #post.comments.each do |comment| %>
3: <b>Comment written by:</b> <%= comment.user.email %><br />
4: <%= comment.body %><br />
5: <% end %>
6:
My schema is as below:-
create_table "comments", :force => true do |t|
t.integer "post_id"
t.integer "user_id"
t.text "body"
.... truncated
end
create_table "posts", :force => true do |t|
t.integer "user_id"
t.integer "sell_or_buy"
t.string "title"
t.text "body"
.... truncated
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
.... truncated
end
My comments create method is as follows:-
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment])
#comment.user_id = current_user.id
redirect_to post_path(#post)
end
end
As you can see I used devise for user model .
Any idea of what I'm doing wrong?Please help me out !!!
I'm using Rails 3.0.1
I believe that you are not saving the #comment after assigning it's user_id. You're doing #comment.user_id = current_user.id, but this change is not reflected in the database.
You could do something like:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
#comment.user_id = current_user.id
#comment.save
redirect_to post_path(#post)
end

Resources