Can partials be controller-specific? - ruby-on-rails

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' %>

Related

Rails new initialized object create an empty record

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.

Render simple_form from partial view on application.html.erb

I want to create a partial view for my registration form and add it to the application layout file because it will be shown in the navigation bar in a dropdown menu.
How can I create this form in a partial view using simple_form gem and render it on application.html.erb?
<%= simple_form_for(#user, url: account_register_path) do |f| %>
Considering that the code above is the way to create the form and I don't know where I should define #user to be used in the application layout nor if I really need it.
Can someone please clarify this?
Don't put it in a partial, just have the registration view on its own, called with render...
#app/views/layouts/application.html.erb
<%= render "accounts/new" %>
#app/views/accounts/new.html.erb
<%= simple_form_for :user, url: account_register_path do |f| %>
...
Whilst you can use a symbol to populate form_for, it won't include the attributes of the model, or the various hidden methods which give context (such as id etc).
If you wanted to populate the accounts#new view/action with a variable, you'll have to set it in your ApplicationController:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_user
private
def set_user
#user = User.new
end
end
This would allow you to use:
#app/views/accounts/new.html.erb
<%= simple_form_for #user, url: account_register_path do |f| %>
--
The best way to implement this would be with Ajax -- this way, the #user object is only created when you need it (resolves bloat).
We've done this before:
You'd just need the following:
#app/views/layouts/application.html.erb
<%= link_to "New User", account_register_path, remote: true %>
#app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
respond_to :js, :html
def new
#user = User.new
respond_with #user
end
end
#app/views/accounts/new.js.erb
$("<%=j render 'accounts/new' %>").appendTo("body");
You have to define #user in the corresponding controller action (I would assume from your code, that controller is called account and action is register), it would be something like:
#user = User.new
In order to initialize instance being created/updated. Then, attributes used in all fields should match users database table.

Using the respond_to method to render a js.erb partial

I have a feature where users like/unlike and dislike/undislike dishes. This is how I initially have my actions setup:
Dishes Controller
class DishesController < ApplicationController
def like
#dish.liked_by current_user
end
def dislike
#dish.disliked_by current_user
end
...
end
Considering the js.erb templates for each template are completely identical I wanted to create just one shared partial that each could use:
Original Setup
# like.js.erb, unlike.js.erb,dislike.js.erb,undislike.js.erb
$(".restaurant__dish-vote--<%= #dish.modifier_class %>").html('<%= escape_javascript(render "restaurants/menu_partials/dish_votes", dish: #dish) %>');
New Setup
# shared/_vote.js.erb
$(".restaurant__dish-vote--<%= #dish.modifier_class %>").html('<%= escape_javascript(render "restaurants/menu_partials/dish_votes", dish: #dish) %>');
Now I'm trying to refactor my controller to reflect these changes but the ajax functionality doesn't seem to work with the following:
class DishesController < ApplicationController
def like
respond_to do |format|
format.js { render partial: "dishes/shared/vote.js.erb" }
end
#dish.liked_by current_user
end
end
The action completes but the changes on the user-facing side are only reflected upon refresh. What am I doing wrong?
I think you should call #dish.liked_by current_user before render partial: "dishes/shared/vote.js.erb". The problem is that #dish is not updated yet.

Instance variable from Rails model: undefined method `any?'

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

routing in rails - displaying the contents of one controller in a different view

I am trying to figure out how to routes the contents of one controller into that of another.
Currently, I have two controllers -
1. Static Pages controller - this is very simple, all it used for is to yield one page (with tabbable pages)
class StaticPagesController < ApplicationController
def home
end
end
2.Guides controller - This is where a user may (currently) add guides to the db.
class GuidesController < ApplicationController
def home
end
def show
#guide = Guide.find(params[:id])
end
def index
#guide = Guide.all
end
def new
#guide = Guide.new
end
def create
#guide = Guide.new(guide_params)
if #guide.save
redirect_to '/guides'
else
render 'new'
end
end
private
def guide_params
params.require(:guide).permit(:title, :description, :image, :date, :date_end, :extra_info)
end
end
I want to display the INDEX part of the controller (which currently works at '/guides', in my root directory, or 'home' in the static pages controller.
I've tried fiddling with all of this, and my last port of call is the routes.rb file. However I am not all together sure, this seems straight forward enough and its cheesing me off not being able to do it.
One of the possible solutions is that you use:
class StaticPagesController < ApplicationController
def home
#guide = Guide.all
end
end
and copy the code from index,html.erb of your guide to home.html.erb of static pages.
other than that you can use rendering: link
EDIT:
use partial to show index of guides:
class GuidesController < ApplicationController
def index
#guide = Guide.all
render partial: "index"
end
end
in your views of guide rename "index.html.erb" to "_index.html.erb" and
in static_pages/home.html.erb add:
<%= render :partial => "guides/index" %>

Resources