How does rails pass instance variable from controller to views? - ruby-on-rails

I created a simple rails app in rails using scaffolding method for restaurants.
This is the show and edit controller method for restaurants_controller.rb. Notice how they are just blank methods:
# GET /restaurants/1
# GET /restaurants/1.json
def show
end
# GET /restaurants/1/edit
def edit
end
This is restaurants/show.html.erb:
<p id="notice"><%= notice %></p>
<%= image_tag #restaurant.image_url %>
<p>
<strong>Name:</strong>
<%= #restaurant.name %>
</p>
<p>
<strong>Address:</strong>
<%= #restaurant.address %>
</p>
...
and restaurants/edit.html.erb:
<h1>Editing Restaurant</h1>
<%= render 'form', restaurant: #restaurant %>
<%= link_to 'Show', #restaurant, class: "btn btn-link" %> |
<%= link_to 'Back', restaurants_path, class: "btn btn-link" %>
Here is my question: my current understanding (could be wrong) is that we define the instance variable, in this case, #restaurant in restaurant_controllers.rb, and Rails automatically connects the variables defined in the controller to views. For example, index method in restaurant controller:
def index
#restaurants = Restaurant.all
end
when I call #restaurants in index.html.erb, Rails brings up #restaurants from index method to be used in views.
Where does rails get the #restaurant instance variable in show.html.erb and edit.html.erb from even though show and edit method in restaurants_controller.rb are empty methods? I am using Rails 5.

So rails acheives this by applying this to generated scaffolds
class TestsController < ApplicationController
before_action :set_test, only: [:show, :edit, :new, :update, :destroy]
private
def set_test
#test = Test.find(params[:id])
end
end
that dose the same as adding
#test = Test.find(params[:id]) to every action if you don't need to..
that then, sets the instance variable on the actions defined by the before_action
im no rails pro, so there may be a much better answer to this, but i have learned to not question "rails magic".

Related

Rails can't create new object inside of ActiveAdmin controller

I'm working on messaging system between User and AdminUser. The User part is ready now I'm struggling how to allow Admin to send a reply to a conversation started by a User, inside of ActiveAdmin.
Code below:
# app/admin/conversations.rb
ActiveAdmin.register Conversation do
decorate_with ConversationDecorator
# ...
controller do
def show
super
#message = #conversation.messages.build
end
end
end
app/views/admin/conversations/_show.html.erb
# ...
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :messageable_id, value: current_user.id, type: "hidden" %>
<%= f.text_field :messageable_type, value: "#{current_user.class.name}", type: "hidden" %>
<%= f.submit "Send Reply" %>
<% end %>
<% end %>
Which gives me an error:
First argument in form cannot contain nil or be empty
Extracted source (around line #51):
51 <%= form_for [#conversation, #message] do |f| %>
When I tried to debug it turned out #message = nil inside of _show.html.erb. How is that possible if I defined #message inside of ActiveAdmin controller ?
[EDIT]
In case you're curious, ConversationController below:
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#admins = AdminUser.all
#conversations = Conversation.all
end
def new
#conversation = Conversation.new
#conversation.messages.build
end
def create
#conversation = Conversation.create!(conversation_params)
redirect_to conversation_messages_path(#conversation)
end
end
#routes
resources :conversations do
resources :messages
end
Normally you set up instance variables in your controller, and then Rails later does an implicit render of the view once the controller method completes.
However, it is possible to do an explicit render of the view, by calling something like render action: or render template: while the controller method is running, and presumably this is happening within the call to super.
See the Layout and Rendering Rails Guide for more information.
You'll need to move the assignment to be before the call to super.
You may also need to replace #conversation with resource in the ActiveAdmin controller (this is an ActiveAdmin/InheritedResources gem thing).

create method is not working for a tweet - contains error "Couldn't find User without an ID"

im making a twitter clone and trying to make it so the users username appears next to their tweet.
Ive made it work through adding a user and a tweet in the seed file, hoever when i add a create,new method and a form it comes up with the error "Couldn't find User without an ID" and highlighting the first line of my create method. not sure what the issue is, thanks.
class TweetsController < ApplicationController
before_action :authenticate_user!, :except => [:index, :new, :create]
def index
#tweets = Tweet.all.order("created_at DESC")
#tweet = Tweet.new
end
def show
#tweet = Tweet.find(params[:id])
end
def new
# #tweet = Tweet.new
end
def create
#user = User.find(params[:id])
#tweet = Tweet.new(tweet_params)
#tweet.user = #user
if #tweet.save
redirect_to tweets_path
end
end
def edit
#tweet = Tweet.find(params[:id])
end
def update
#tweet = Tweet.find(params[:id])
#tweet.update(tweet_params)
redirect_to tweets_path
end
private
def tweet_params
params.require(:tweet).permit(:user_id,:content)
end
end
<h1>TWEETS</h1>
<%= simple_form_for [#user,#tweet], id: "form-submit" do |f| %>
<%= f.input :content, label: "Tweet" %>
<%= f.input :user %>
<%= f.button :submit, class: "btn btn-danger" %>
<% end %>
<br>
<% #tweets.each do |tweet| %>
<ul>
<li>
<%= tweet.created_at.strftime("%B %d %Y, %l:%M%P") %> <br>
<%= tweet.content %>
<%= tweet.user.username %>
</li>
</ul>
<% end %>
You need to define #user in a variable in your index method.
Any variable you use in the form needs to be declared somewhere, either in the helper, controller, or view. Rails convention is to declare them in the controller normally.
I would need to see your config/routes.rb file for the error message you are getting in the image, but if you type rails routes at the command line, you can see a list of all available routes, when you use:
simple_form_for [#user, #tweet]
Rails will interpret [#user, #tweet] as user_tweets_path, and try to submit the form to this path. That path is defined in your config/routes.rb file.
The error is telling you that you have not defined this path in the routes file. To define this path you can add this line to your routes file:
resources :users do
resources :tweets
end

Undefined method link_to_edit using Draper decorator

I've got a User and Post models, which are related to each other in a classical way -- User has_many :posts and Post belongs_to :user. In my users#show, where I display a user's profile, I also have a list of all posts he has made. Also, I wanted to have links to edit and delete each post respectfully. So, I made up with this:
<% #user.posts.each do |post| %>
<h1><%= link_to post.title, post_path(post) %></h1>
<% if #user == current_user %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post_path(post), method: :delete %>
<% end %>
<% end %>
But surely placing this logic into view results in a mess, so I decided to use Draper and write decorators for that. As we are going to check rights for posts#edit and posts#delete methods, I came up with a decorator for Post model and tried to use it in PostsController. Here it goes:
class PostDecorator << Draper::Decorator
delegate_all
def link_to_edit
if object.user == current_user
h.link_to 'Edit', h.edit_post_path(object)
end
end
def link_to_delete
if object.user == current.user
h.link_to 'Delete', h.post_path(object), method: :delete
end
end
end
Then, in my PostsController:
# ... class definition
before_action :set_post, only: [:show, :edit, :update, :destroy]
# ... other controller methods
def edit; end
def update
if #post.update(post_params)
#post.save
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to feed_path
end
private
# Using FriendlyId gem to have neat slugs
def set_post
#post = Post.friendly.find(params[:id]).decorate
end
But every time I try to render my User profile with list of his posts, with the use of my new helpers <%= post.link_to_delete %> and <%= post.link_to_edit %> instead of that conditional mess, it just returns me the following error:
What am I doing wrong?
You probably figured this out in the meantime but here's an answer for others: You were calling #post = ....decorate in your controller but you are using #user.posts.each { |post| ... } in your view. The objects fed to that block are not decorated. Only #post is.
In your view you should have done something like #user.posts.each { |raw_post| post = raw_post.decorate } and so on. Obviously, with ERB syntax. Or #user.decorated_posts.each ... where
class User < ActiveRecord::Base
...
def decorated_posts
# this will load every post associated with the user.
# if there are a lot of them you might want to only load a limited scope of them
posts.map(&:decorate)
end
...
end

Rails Ancestry Gem cannot access child/parents

I've been trying to get the ancestry gem for rails to work but I seem to fail at implementing even the most simplest case. I'm new to rails so I could easily be making a rookie mistake.
What I was trying to implement was just a normal commenting system where replies to comments would be shown all on the same page using ancestry's child/parent functionality.
Below is my show html which has a reply link which is where I am trying to set up the parent child relationship:
<p id="notice"><%= notice %></p>
<%=#comment.children.nil?%>
<p>
<strong>Body:</strong>
<%= #comment.body %>
</p>
<p>
<%if !#children.nil?%>
<% #children.each do |c|%>
c.body
<%end%>
<%end%>
</p>
<p>
<strong>Author:</strong>
<%= #comment.author %>
</p>
<%= link_to 'Reply', new_comment_path, :parent_id => #comment %>
<%= link_to 'Edit', edit_comment_path(#comment) %> |
<%= link_to 'Back', comments_path %>
And here is some of the controller methods:
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# GET /comments
# GET /comments.json
def index
#comments = Comment.all
end
# GET /comments/1
# GET /comments/1.json
def show
#children=#comment.children
end
# GET /comments/new
def new
#parent_id = params.delete(:parent_id)
#comment = Comment.new(:parent_id => #parent_id)
end
//omitted code
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
I can not seem to get any parent/child relationship with the above code
ok I figured it out in the end. I was only passing the parent parameter to the "new" controller method, I should have been passing it again after this at the create stage. Was a silly mistake.

Hartl's rails tutorial error "Couldn't find User with id=#<User:0x00000007472a28>"

I'm working through Michael Hartl's ruby on rails tutorial without too much trouble (at least not much I haven't been able to solve with a bit of looking/thinking/searching) but I'm stuck on an error I get when trying to implement the relationships model in chapter 11.
Specifically, hitting the follow/unfollow button returns the following error:
ActiveRecord::RecordNotFound in RelationshipsController#create
Couldn't find User with id=#<User:0x007f3288020d98>
Extracted source (around line #5):
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
redirect_to #user
end
I can tell that the problem is that the userid it's searching should just be an integer, not anything like #, but I'm not sure why it's searching something that looks like a hex code instead of the userid.
The RelationshipsController is exactly the same as given in the tutorial:
class RelationshipsController < ApplicationController
before_action :signed_in_user
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
redirect_to #user
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
redirect_to #user
end
end
The only significant difference I can think of is that I'm using postgres instead of sqlite, but since switching to postgres was given as an exercise I can't imagine it would cause this sort of problem.
I tried searching for similar issues, but I couldn't find anything - any help would be great.
(edit)
The form that renders the follow/unfollow buttons is:
<% unless current_user?(#user) %>
<div id="follow_form">
<% if current_user.following?(#user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
And the buttons themselves:
<%= form_for (current_user.relationships.build(followed_id: #user)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
and
<%= form_for(current_user.relationships.find_by(followed_id: #user), html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
The show method of the UsersController is
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def show
#user = User.find(params[:id])
#microposts = #user.microposts.paginate(page: params[:page])
if current_user?(#user)
#micropost = current_user.microposts.build
end
end
.
.
.
You want the id of the user not the user object itself so change the partial for the follow to have:
followed_id: #user.id instead of followed_id: #user

Resources