Rails 4 passing variables in the views or templates - ruby-on-rails

I have an app that will allow the owner (but not public users) to upload photo album files and photos. When coding my views, I noticed something strange.In the do block in my albums/index.html.erb file, if I pass in the variable #album.id, I get a NoMethodError for NilClass.Yet, if I remove the "#", (or remove that variable entirely), it works fine.
But in my albums/show.html.erb file, in the link_to line of code for editing an album title, I need the "#album.id" to be passed (or the variable left out entirely) in order for it to work.
Why is that?
Here is my albums/index.html.erb file and code
<div class="admin_login"><%= link_to "Admin Login", new_album_path %></div>
<div class="home"><%= link_to "Home", root_path %></div>
<h1>Albums Gallery</h1>
<% #albums.each do |album| %>
<div>
<%= link_to album.name, album_path(album.id) %>
</div>
<% end %>
And here is my albums/show.html.erb file:
<h3><%= link_to #album.name %></h3>
<div class="album">
<%= link_to "Edit", edit_album_path(#album.id) %>
<%= link_to "Delete", #album, method: :delete, data:{confirm: "Are you sure you want to delete this album? All photos in it will be permanently deleted!"} %>
</div>
<br><%= link_to "Back", albums_path %>
For clarity, here is my albums controller code:
class AlbumsController < ApplicationController
def index
#albums = Album.all
end
def new
#album = Album.new
end
def create
#album = Album.new(album_params)
#album.save
redirect_to albums_path
end
def show
#album = Album.find(params[:id])
end
def edit
#album = Album.find(params[:id])
end
def update
#album = Album.find(params[:id])
if #album.update(album_params)
redirect_to album_path(#album.id)
else
render 'edit'
end
end
def destroy
#album = Album.find(params[:id])
#album.destroy
redirect_to albums_path
end
private
def album_params
params.require(:album).permit(:id, :name, :category)
end
end

In your index action, you're defining a series of albums as #albums. In your show action, you define just a single #album. These variables are accessible only in the action in which they are defined.
The reason that 'album' works in your index view is that the each block is defining a local 'album' variable within the block's scope.
<% #albums.each do |album| %>
<div>
<%= link_to album.name, album_path(album.id) %>
</div>
<% end %>
That |album| after the do block says "for this iteration, assign the current value to the variable album"

You need to setup instance variables in the controller. Make sure you set #albums in AlbumsController index action and #album in show action. The do block uses block variable, not instance, so there is no need for #.

Related

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

How can I redirect to the object in an array

I have action index:
def index
if params['type'] == 'random'
#objects = Object.order("RANDOM()").limit(1)
else
#objects = Object.all.limit(1)
end
end
and create action:
def create
object = Object.find(params[:object_id])
comment = object.comments.create(params[:comment].permit(:body))
respond_to do |format|
format.html
format.js #ajax
end
if comment.save
redirect_to root_path(params[:object_id]) #doesn't work
else
flash[:error] = comment.errors.full_messages[0]
redirect_to root_path(params[:object_id]) #doesn't work
end
end
I can comment an object in my index page. When I put a comment, I want to redirect to the object that was commented.
With my code, the page is reloaded, but the next object is displayed, and I cannot see the comment. How can I redirect to the same object?
My root_path
<span class="random-icon"><%= link_to icon('random'), "http://localhost:3000/?type=random" %></span>
<div class="inner-container">
<% #objects.each do |object| %>
<h1 class="title"><%= object.title %></h1>
<p class="obj"><%= object.body %></p>
<h3 class="comments-title">Comments:</h3>
<div id="comments">
<% object.comments.each do |comment| %>
<div class="comments"> <%= comment.body %>
<span class="time-to-now"><%= distance_of_time_in_words_to_now(comment.created_at) %> ago</span>
</div>
<% end %>
</div>
<div id="error"><%= flash[:error] %></div>
<%= form_for([object, object.comments.build], remote: true) do |f| %>
<%= f.text_area :body, class: "text-area" %>
<p class="char-limit">255 characters limit</p>
<%= f.submit "Comment", class: 'button' %>
<% end %>
<% end %>
</div>
If params['type'] is true, Object.order("RANDOM()").limit(1) will always br reevaluated and usually return a new object. To ensure you return to the same object, you might want to store it in session and then check first in your index if there is a liked comment in your sessions, if so, #objects = Object.find(session[:comment_object_id])
def index
if session[:comment_object_id]
#objects = Object.find(session[:comment_object_id])
session.delete(:comment_object_id) # delete the session after use
elsif params['type'] == 'random'
#objects = Object.order("RANDOM()").limit(1)
else
#objects = Object.all.limit(1)
end
end
def create
object = Object.find(params[:id])
comment = object.comments.create(params[:comment].permit(:body))
respond_to do |format|
format.html
format.js #ajax
end
if comment.save
session[:comment_object_id] = :object_id # set the session here
redirect_to root_path # should work now
else
flash[:error] = comment.errors.full_messages[0]
redirect_to root_path #should work now
end
end
This is pretty easy, and you're very close.
Inside your create method, you've got the object you want to redirect to. So just use it directly inside the if comment.save like:
redirect_to object_path(object)
You can get a list of all these path "helpers" via the command:
rake routes
And in that listing, you should see, by the way, that root_path does not accept any arguments ... for future reference.

writing method for destroy and creating link to destroy the object

I am building presentation builder, and I don't know how to destroy the chosen presentation presented in the list when I click on the button Remove.
My controllers looks like that:
def create
if logged_in?
presentation = current_user.presentations.create data: params.to_yaml
redirect_to edit_presentation_path(presentation)
end
end
def edit
render action: :new
end
# def destroy
# current_presentation.destroy
# end
def show
render action: :new
end
def list
#presentations = current_user.presentations
end
def update
current_presentation.update_attributes(data: params.to_yaml)
end
def home
if logged_in?
#presentations = current_user.presentations
end
end
My list of created presentations looks like that:
<% #presentations.each do |p| %>
<a > <%= p.id %>
Show
<a class="action"> Remove </a>
</a>
<% end %>
My goal is: to write correct destroy method and create a link Remove that executes this method for a particular presentation.
<%= link_to "Delete", p, method: :delete %>
Something like that should do it.
More here http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html
<%= link_to "Delete", your_destroy_path(p), method: :delete %>

how to catch mongoid validation erros from another controller?

I have 2 controllers: DocumentsController and DashboardController
After the user login successful, he is redirected to dashboard_path, which has a form to create a 'fast document' like this
<%= form_for #document, :html => {:class => 'well'} do |f| %>
<% if #document.errors.any? %>
<div id="alert alert-block">
<div class="alert alert-error">
<h2>Couldn't create your doc. :(</h2>
<ul>
<% #document.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
</div>
<% end %>
<label>A lot of fields</label>
<%= f.text_field :fields %>
<div class="form-actions">
<%= f.submit 'Create document', :class => 'btn btn-large' %>
</div>
<% end %>
but when an exception happen (like the user forgot to fill a field), I would like to show these exceptions, not just an alert saying 'Error'...actually, I didn't found a way to do this
here's my DashboarController
class DashboardController < ApplicationController
before_filter :authenticate
def index
#document = Document.new
end
end
and my DocumentsController
class DocumentsController < ApplicationController
respond_to :json, :html
def show
end
def create
#document = Document.new(params[:document])
#document.user = current_user
if #document.save
redirect_to dashboard_path, notice: 'Created!'
else
flash[:error] = 'Error!'
redirect_to dashboard_path
end
end
end
any help is appreciated :)
You are correctly redirecting on success; on failure, though, should not redirect; you need to render the template where the form was filled.
if #document.save
redirect_to dashboard_path, notice: 'Created!'
else
render 'dashboard/index'
end
You'll have to make sure that any variables needed by the index template are available in the create action of the documents_controller (you're just rendering the index template; you're not running the code from the dashboard controller's index action). Here's a excerpt from the relevant Rails Guide to clarify:
Using render with :action is a frequent source of confusion for Rails newcomers. The specified action is used to determine which view to render, but Rails does not run any of the code for that action in the controller. Any instance variables that you require in the view must be set up in the current action before calling render.
More at http://guides.rubyonrails.org/layouts_and_rendering.html#using-render

Rails app gives "no method error" when it sees <% if current_user.admin? %>

I used this code in my "users" views and had no trouble: <% if current_user.admin? %>. But using it in a set of views associated with a different controller throws up the "No method Error."
Background: the app allows admins to create scavenger hunts. Admins should be able to delete hunts. I thought I knew how to configure everything, but apparently, I'm missing something. Here's my code:
controller.rb
class HuntsController < ApplicationController
def index
#title = "All Hunts"
#hunts = Hunt.order("name ASC")
end
def show
#hunt = Hunt.find(params[:id])
#title = #hunt.name
end
def new
#hunt = Hunt.new
#title = "New Hunt"
end
def create
#hunt = Hunt.new(params[:hunt])
if #hunt.save
flash[:success] = "Hunt created!"
redirect_to hunts
else
#title = "New Hunt"
render 'new'
end
end
def edit
#hunt = Hunt.find(params[:id])
#title = "Edit hunt"
end
def delete
Hunt.find(params[:id]).destroy
flash[:success] = "Hunt destroyed."
redirect_to index
end
end
Views/Index.html.erb
<h1>All Hunts</h1>
<ul>
<% #hunts.each do |hunt| %>
<%= render hunt %>
<% end %>
</ul>
<%= link_to( "Create New Hunt", '/hunts/new') %>
Views/_hunt.html.erb
<li>
<%= link_to hunt.name, hunt %>
<% if current_user.admin? %>
<%= link_to "delete", hunt, :method => :delete, :confirm => "You sure?",
:title => "Delete #{hunt.name}" %>
<% end %>
</li>
Error Message when trying to head to /hunts:
NoMethodError in Hunts#index
Showing ...../app/views/hunts/_hunt.html.erb where line #3 raised:
undefined method `admin?' for nil:NilClass
current_user is nil, and thus does not know how to respond to admin?. Either ensure that current_user is always a user instance, or check that it's not nil.
In Ruby 2.3+, one can use the “safe navigation” operator (&.):
if current_user&.admin?
In Ruby 2.2 and earlier, instead use boolean short-circuiting:
if current_user && current_user.admin?
Note that ActiveSupport has try, but that has different behavior which will potentially hide bugs. For similar behavior, use try! instead.
Getting "undefined method _____ for nil:NilClass" is a very common occurrence in Ruby, so get used to it happening often :).
You have to sign in the user in order to instantiate current_user. If you are using devise, use:
class HuntsController < ApplicationController
before_filter :authenticate_user!
def index
...
end
.......
end
in your controller. And make sure that .admin? method is defined in your User model.

Resources