Trying to display some affiliate products while keeping my controllers as skinny as possible. But does anybody know why this isn't working?
undefined method `any?' for nil:NilClass
app/models/shopsense.rb
require "rest_client"
module Shopsense
def self.fetch
response = RestClient::Request.execute(
:method => :get,
:url => "http://api.shopstyle.com/api/v2/products?pid=uid7849-6112293-28&fts=women&offset=0&limit=10"
)
# !!! ATTENTION !!!
#
# It works if I put the below in `shopsense` in the controller instead
#
#products = JSON.parse(response)["products"].map do |product|
product = OpenStruct.new(product)
product
end
end
end
app/controllers/main_controller.rb
class MainController < ApplicationController
before_action :shopsense
def index
end
def shopsense
Shopsense.fetch
end
end
app/views/main/index.html.erb
<% if #products.any? %>
<% #products.each do |product| %>
<div class="product">
<%= link_to product.name %>
</div>
<% end %>
<% end %>
Your index.html.erb is requesting an instance variable #products, which isn't available through in the index action of your controller.
Put the instance variable in your index action:
def index
#products = Shopsense.fetch
end
Instance variables don't belong in a model. So you can not use #products there. Put it back into the controller and you are fine.
Correct - instance variables in rails declared in the controller are available in the view. In your case, you are declaring the instance variable inside a module, and not the controller.
Try this:
def index
#products = shopsense
end
In this case, your controller will pass on the #products instance variable to the view
Because #products should be a member of MainController to be visible inside the view.
This should work:
class MainController < ApplicationController
before_action :shopsense
...
def shopsense
#products = Shopsense.fetch
end
end
Another option is to include the Shopsense module into MainController:
module Shopsense
def fetch
...
end
end
class MainController < ApplicationController
include Shopsense
before_action :fetch
...
end
Related
I have two model called TodoList and TodoItem. In the TodoItem index page, i'm showing new form and list of todo items. Everything works perfect But it generate an empty record while in browser.
class TodoItem < ApplicationRecord
belongs_to :todo_list
end
class TodoList < ApplicationRecord
has_many :todo_items, dependent: :destroy
end
controllers have:
class TodoItemsController < ApplicationController
def index
#todo_list = TodoList.find(params[:todo_list_id])
#todo_items = #todo_list.todo_items
#new_todo = #todo_list.todo_items.new
end
def create
#todo_list = TodoList.find(params[:todo_list_id])
#todo_item = #todo_list.todo_items.new(params.require(:todo_item).permit(:description, :complete_at))
if #todo_item.save
redirect_to todo_list_todo_items_path(#todo_list)
end
end
end
index.html.erb
<div>
<div>
<% form_with(model: [#todo_list, #todo_item], local: true) do |f| %>
<% f.text_field :description %>
<% f.submit %>
<% end %>
</div>
<ul>
<% #todo_items.each do |todo_item| %>
<li><%= todo_item.description %></li>
<% end %>
</ul>
</div>
class TodoItemsController < ApplicationController
# use callbacks instead of repeating yourself
before_action :set_todolist, only: [:new, :create, :index]
def index
#todo_items = #todo_list.todo_items
#todo_item = TodoItem.new
end
def create
#todo_item = #todo_list.todo_items.new(todo_list_params)
if #todo_item.save
redirect_to [#todo_list, :todo_items]
else
render :new
end
end
private
def set_todolist
#todo_list = TodoList.find(params[:todo_list_id])
end
# use a private method for your params whitelist for readibility
# it also lets you reuse it for the update action
def todo_list_params
params.require(:todo_item)
.permit(:description, :complete_at)
end
end
You where setting a different instance variable (#new_todo) in you index action. The polymorphic route helpers that look up the route helpers from [#todo_list, #todo_item] call compact on the array. So if #todo_item is nil its going to call todo_lists_path instead - ooops!
You alway also need to consider how you are going to respond to invalid data. Usually in Rails this means rendering the new view. If you are rendering the form in another view such as the index view it can get kind of tricky to re-render the same view as you have to set up all the same data as that action which leads to duplication.
It seems #new_todo has been added to #todo_items somehow in index action:
def index
#todo_items = #todo_list.todo_items
#new_todo = #todo_list.todo_items.new
# The above line has a side effect: #todo_items = #todo_items + [#new_todo]
end
I'm not sure it's a bug or feature from Rails (I use Rails 6.1.1).
For a quick fix, you can change #todo_list.todo_items.new to TodoItem.new.
In Rails, can partials be controller-specific? I have a controller for creating post and a view that went with it. After I renamed the view (added _) to make it a partial, it no longer seems to work.
Thank you in advance.
Partials are not views.
Partials cannot be rendered as views. Which makes perfect sense since partials are not views - they are reusable chunks of a view.
Lets say you have:
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
#post = Post.new
end
end
# app/views/posts/new.html.erb
<%= form_for(#post) do |f| %>
# ...
<% end %>
If you rename new.html.erb -> _new.html.erb Rails will no longer be able to find the view - because it no longer is a view.
Can partials be controller-specific?
Partials are by there very nature controller-specific. For example:
# app/views/posts/show.html.erb
<%= render partial: 'foo' %>
Will look for app/views/posts/_foo.html.erb.
# app/views/stories/show.html.erb
<%= render partial: 'foo' %>
Will look for app/views/stories/_foo.html.erb.
Thats because Rails prepends the view lookup path with app/views/controller_name.
Can views be shared?
Yes. But you need to explicitly tell Rails where the view is located.
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
#resource = Post.new
render 'resources/new'
end
end
# app/controllers/stories_controller.rb
class StoriesController < ApplicationController
def new
#resource = Story.new
render 'resources/new'
end
end
Or you could append the view path.
class ResourcesController < ApplicationController
prepend_view_path Rails.root.join('app/views/resources')
end
class PostsController < ResourcesController
def new
#resource = Post.new
# will now default to rendering `app/views/resources/new.html.erb`
end
end
Can partials be shared?
Yes. But you need to tell Rails where the partial is located.
<%= render partial: 'shared/social_media_icons' %>
I have a user profile controller called "userinfo" and it's corresponding view. The userinfo index is the root path. In the homepage(which is the userinfo index), I have a link that takes you to the user profile page. It is giving me this error when I click on the image on the view page:
My routes are:
My userinfos_controller:
class UserinfosController < ApplicationController
before_action :find_userinfo, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#userinfors = Userinfo.where(:userinfo_id => #userinformation_user_id)
end
def show
#myvideo = Video.last
end
def new
#userinformation = current_user.userinfos.build
end
def create
#userinformation = current_user.userinfos.build(userinfo_params)
if #userinformation.save
redirect_to root_path
else
render 'new'
end
end
def edit
end
def update
end
def destroy
#userinformation.destroy
redirect_to userinfo_path
end
private
def userinfo_params
params.require(:userinfo).permit(:name, :email, :college, :gpa, :major)
end
def find_userinfo
#userinformation = Userinfo.find(params[:id])
end
end
and my view is:
<%= link_to image_tag("student.png", class: 'right'), userinfo_path(#userinfors) %>
I thought maybe I must include ':index' in the 'before_action :find_userinfo' at the top of my controller. If I do that, the homepage doesn't even load and it gives me this error:
Try below code:
controller
def index
#userinfors = Userinfo.where(:userinfo_id => #userinformation_user_id) #pass id instead of object #userinformation_user_id
end
view
<% #userinfors.each do |u| %>
<%= link_to image_tag("student.png", class: 'right'), userinfo_path(u) %>
<% end %>
Your problem is that you're trying to do perform a lookup based on something that's not an ActiveRecord (database) attribute.
Your root goes to UserinfosController which expects #userinformation_user_id but I can't tell from your code where that comes from.
You need to define your route in order that this will be expecting for an specific param, maybe the user id, and then you're able to add the value within your view, in a link_to helper:
You could modify your routes.rb to expect an id as param:
get '/user_infors/:id', to: 'userinfos#index', as: 'userinfo_path'
Then in your controller, use a find to "find" in the database the user with such id. If you'd like to use where then that would give you a relationship with all the userinfos with the id being passed as param.
If you want so, then use Userinfo.where('userinfo_id = ?', params[:id]):
def index
#userinfors = Userinfo.find(params[:id])
end
And then in your view you can access to #userinfors:
<% #userinfors.each do |user| %>
<%= link_to image_tag 'student.png', class: 'right', userinfo_path(user) %>
<% end %>
I think you could define the index to get all the userinfors and a show method to get an specific one, as you're trying to do.
I cannot seem to find the problem. In my venues show template, I want to show the venue name, and under that, I list all the venues in the database
<%= venu.name %>
<% #venus.each do |v| %>
I get the error that #venus is nil... but it is defined in my controller:
undefined method 'each' for nil:NilClass
venues_controller.rb
class VenuesController < ApplicationController
before_action :find_venue, only: [:show, :edit, :update, :destroy]
def index
#venus = Venue.all
end
def show
render :layout => nil
#venus = Venue.all
end
def new
#venu = Venue.new
end
def create
#venu = Venue.new(venue_params)
#venu.save
end
def edit
end
def update
end
def destroy
end
private
def venue_params
params.require(:venue).permit(:name, :phone, :address, :description, :type)
end
def find_venue
#venu = Venue.find(params[:id])
end
end
I have a resources :venues route in my routes.rb.
I am not sure what is causing this problem.
In your show method, you should render at the end
def show
#venus = Venue.all
render :layout => nil
end
Remove render :layout => nil from your show action.
And in your view, you need to use the instance variable #venu instead of venu
<%= #venu.name %>
I wonder why you use two instance variables for action show
#venu (via find_venue before_filter) & #venus via the action itself.
Best practice would be removing this line from action show, since show action normally used to show details for one element from a list.
#venus = Venue.all
and use #venu set by the before_filter instead.
But if you do want to keep both then re-order the lines in show action
#venus = Venue.all
render :layout => nil
Also, change the venu to #venu in the show.html.erb and if you like correct the typo in the instance variables #venu => #venue :) (Could happen to any of us)
Usually in the index method, it should show all the venus, and in the show method it would show detailed view of each venue.
Try setting something like this:
def index
#venus = Venue.all
end
def show
render :layout => nil
#venue = Venue.find(params[:id])
end
now in show.html.erb you should be able to use
#venue.name
and in your index.html.erb, you can iterate over the venus like so:
<% #venus.each do |v| %>
<%= link_to v do %>
<%= v.name %>
<% end %>
<% end %>
The above answer is correct. You can use #venus = Venue.all in your show view but because you render first it throws you an error. Just render at the end.
I am facing problems with undefined method `to_key'
this is my books_controller.rb
class BooksController < ApplicationController
def index
#books = Book.where(user_id: current_user.id)
end
end
and my index page as follow.
index.html.erb
<div>
<%= form_for #books do |f| %>
...
...
<% end %>
</div>
now when I am going to access index page that time I got an error as follow.
undefined method `to_key' for #<Book::ActiveRecord_Relation:0x007fb709a6a8c0>
index usually returns a collection. And indeed, your controller conforms. However, your view tries to define a form for it. This is not going to succeed, as you find out. Forms are for entities, not for collections. The bug is in your view and how you expect to handle index.
Should be:
class BooksController < ApplicationController
def index
#book = Book.find_by_id(2)
end
or
def index
#book = Book.new
end