Where to add shared view files in Rails 5? - ruby-on-rails

I am using Rails 5 and I want to use shared files in my project. I have searched the web about this, and I got some results as follow:
For Rails 3
app/views/shared
For Rails 4
app/views/application
but there was nothing for Rails 5.
I am talking about the standard; is there any specific location to add common view files in Rails 5, just as we do in other frameworks? Like in Laravel there is a layout folder?

All the people telling you that there is no "standard location" for common view files are correct. However, if you want to ask Rails where it would go looking for a partial file if you attempted to render one on the view, just take any view file you have and render a partial you know doesn't exist.
If you have, say, a UsersController with a view structure that looks something like app/view/users, and you add the following on any view there:
<%= render 'foo' %>
You'll likely get an error that says something like:
Missing partial users/_foo, application/_foo
That means that whenever you attempt to render a partial by just it's name without any path information, Rails will look for it
first under the view directory for the current controller, in this case users
then under the application directory
So, I guess then you could say that the "default location" in Rails for common view files is under the application directory.
It's possible to add to this in order to create your app's own "standard location" for common view files by overriding the ActionView::ViewPaths.local_prefixes private class method that gets mixed in to every controller in Rails:
class ApplicationController < ActionController::Base
def self.local_prefixes
['shared', controller_path]
end
private_class_method :local_prefixes
end
class UsersController < ApplicationController
def self.local_prefixes
[controller_path]
end
private_class_method :local_prefixes
end
Now, your error will say something like:
Missing partial users/_foo, shared/_foo, application/_foo
which shows that Rails will check the shared directory for partials as well before it goes looking in the application directory.
Note that this method override should happen in both the controllers, otherwise the local_prefixes in UsersController get inherited from ApplicationController and you end up with duplicated lookup paths that look like:
shared/_foo, users/_foo, shared/_foo, application/_foo
If all this is too much effort/too weird, as other people have pointed out, you can always just specify your shared partial directory manually when you render a partial:
<%= render 'shared/foo' %>

You can create new directory in app/views directory and add your shared file in that directory.
Ex:
app/views/shared # Contains all shared/partial pages
Now create your partial page in app/views/shared directory
like: app/views/shared/_my_shared_view.html.erb
Now if you want use this shared view in any view just add the below code.
<%= render :partial => 'shared/my_shared_view' %>
That's it. Hope this will help you.

Related

Rails Controller Confusion

this is my first post.
I'm brand new to Rails and I'm attempting to learn how to use it. To be clear, I have a brushing familiarity with Ruby. I'm pretty sure I get the MVC structure, but I'm having trouble understanding certain behaviors I'm experiencing.
Just in case anyone learned from the same source, I'm watching Derek Banas explain it. He explains the thing I'm having trouble with around 16:20. https://www.youtube.com/watch?v=GY7Ps8fqGdc
On to specifics- So I placed this line in my routes.rb file:
match':controller(/:action(/:id))', :via => :get
and I created an instance variable in the controller using this:
def sample
#controller_message = "Hello From The Controller"
end
And in a sample view I created, I call on the "controller_message" variable like this:
<%= "#{#controller_message}" %>
And it works on that one view half the time. Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right? Maybe I just don't understand how this functions, but I made other view files in the same directory in an attempt to see how controllers pass data to views. They load and everything, but I'm not getting the message from the controller. Sometimes, seemingly inconsistently, the controller message won't even display on the first view where it worked originally, especially if I navigate around the site a little. To get it to display that message again, I have to restart my server.
So am I just misunderstanding how MVC works, or is my software glitching (unlikely, I know), or what? I'm so confused.
I've heard so many great things about this community. Thanks in advance to anyone willing to help me. I'm so stressed out.
The #{} in <%= "#{#controller_message}" %>is string interpolation. The usual convention of displaying an instance variable in a view is simply <%= #controller_message %>
The variable #controller_message, declared in the sample method, makes that variable available to the view associated with that method. By default, rails will look for a corresponding view file that has the same name as the controller method, so in this case it will look for a view called sample.html.erb in the app/views/your_controllers_name folder
Well, to clarify things (because your question is a little vague): rails does a lot of stuffs behind the scene, like relating controller's name with view's name. That is why, in most case, you don't invoke a view to be render in controller's method (Rails does that to you based on file's name). So, would make sense the variables declared in a controller method be displayed only in the view with the same name as the controller method called. If you want to display a variable in a view that is not related with the controller method, you should invoke that view with methods like Render and Redirect, and pass the variables as arguments to those methods.
I.g:
other.controller
def edit
render "/another_view_folder/example.html.erb", #variable_to_be_displayed
end
Another thing:
<%= #controller_message %>
This is enough to display the variable. The way you were doing was Interpolation (use to concatenate variable with strings).
I hope I can help you!
Rails will implicitly render a view file if it is found in the view path.
So given:
# config/routes.rb
get 'foos/bar'
# app/controllers/foo_controller.rb
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
end
end
Rails will attempt to find the file bar.html.erb in app/views/foos when we request /foos/bar. If we want to render another view we need to tell rails to render it explicitly.
class FoosController < ApplicationController
def bar
#controller_message = 'Hello'
render 'some_other_view' # renders app/views/foos/some_other_view.html.erb
# or
render 'some_other_folder/some_other_view' # renders app/views/some_other_folder/some_other_view.html.erb
end
end
Now from what I understand, I should see "Hello From The Controller" anywhere that line of code is placed in a view, right?
No. Lets say you add another controller and view.
# config/routes.rb
get 'people', to: 'people#index'
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
def index
end
end
# app/views/people/index.html.erb
<h1>The message is: <%= #controller_message %></h1>
It will just render The message is:. Since PeopleController does not set the instance variable #controller_message. Ruby will not raise an error if you reference an unassigned instance variable.
The way to reason about this is that the controller in Rails packages all its instance variables and passes them to the view context so that we can use them in the view.
After the controller has finished rendering it sends the rendered template and the program exits*. Instance variables, local variables etc, do not carry over to the next request.

Ruby on Rails views names dependences

May be my question it's a little weird for RoR developers, but i'm new in Ruby on Rails, and i only discover this world now - there is some dependencies in views names and definitions in controller?
If i have, for example, view called "parse-public-profile.html.erb" , should i add in controller definition with exactly this name? i mean "def parse-public-profile ... end"
I know, that this is basic, but simply i try to understand how controller knows, what views i have now; what i should change, if i will add/change-name of view, or how to define view, if in my "views" folder, i have another folder, for ex. "clients"
Thanks!
Rails follows REST this means methods as index, show, edit, update, destroy etc. are very common in an Rails controller. When you have a custom action(method) however on your controller Rails will look for the corresponding view file, so for example:
class UsersController < ApplicationController
def another_action
end
end
will try to render: app/views/users/another_action.html.erb
There is also the concept of partials which are normally called within a view file f.e. in users/index.html.erb
<% render :partial => 'form' %>
will try to render: app/views/users/_form.html.erb (note the _)
An in depth explanation can be found in the Rails guides
You can also use:
def index
render :template => "users/parse-public-profile"
end
The :template over rides the default file that Rails would have rendered.
For more info, see the Rails Guide on Layouts and Rendering at http://guides.rubyonrails.org/layouts_and_rendering.html.

Identical Files behave differently due to link with controller

I am building my first app with ROR and stumbled upon a couple of problems due to my understanding of the MVC
I have a page to add a new item, and this works fine, rails magically hooks it up to the items controller and somehow by magic it knows to look in the method 'new' as the page is called new.
But this layer is confusing me, as i need to now create a different version of new, same functionality but with a different look so to use a different layout to application.html.erb
So i attempt to create a copy of new.html.erb and create bookmarklet.html.erb - both contain exactly the same code: a link to a form. but of course bookmarklet will error on me because it does not have that link in the controller - how do i 'wire' up bookmarklet so that i can call the new method and so that it can behave in a similar way to the identical new.html.erb
Also, how can i tell the new bookmarklet.html.erb to ignore the application.html.erb and get its layout from another file?
thanks in advance
The magic happens in the routes. Rails uses something called RESTful routes, which is taking HTTP verbs and assigning standard actions to it. the new action is a GET request in HTTP speak, and if you are using scaffolding or following REST, will have the ruby call to build a new object in the controller, as an instance variable so you can use it in your view.
You have to tell rails routes that you want to BREAK this arrangement and to let /items/bookmarklet be used in the controller.
In your routes.rb file replace resources :items with
resources items do
member do
get 'bookmarklet'
end
end
In your controller put:
def bookmarklet
#item = Item.new
render :template => "bookmarklet", :layout => "different_layout" # or you can put this on the top of the controller, which is my style, but whatevs.
end
You should look into partials, if you are doing this as they clean up your code immensely.
A better way to think of things is to start with the controller instead of the view html.erb files. So each public method in your controller is effectively a page or action in the site. When you need a new action or page, add the method to the controller first. Then create the relevant view files.
So in your controller you'll need something like:
def bookmarklet
#item = Item.new(params[:item])
#item.save
render :template => "items/bookmarklet.html.erb", :layout => "different_layout.html.erb"
end
Normally you don't need to call render manually in the controller, but since you want a different layout than the default you need to specify it using render.

Where to put partials shared by the whole application in Rails?

Where would I go about placing partial files shared by more than one model?
I have a page called crop.html.erb that is used for one model - Photo.
Now I would like to use it for another model called User as well.
I could copy and paste the code but that's not very DRY, so I figured I would move it into a partial.
Since it's shared between two models - where would I place that partial?
Thanks!
The Rails convention is to put shared partials in /app/views/shared.
Update
Layout inheritance is now in the guides under layout and rendering
Template inheritance works similarly.
Rails 3.1 and following versions implement template inheritance, so I think the correct place for shared partials is now /app/views/application/, say you are in products#index you can do the following:
-# products#index
= render #products.presence || 'empty'
-# /app/views/application/_empty.html.haml
There are no items
btw it's application because the connection is the controller inheritance, so this assumes ProductsController < ApplicationController
This way if you implement /app/views/products/_empty.html.haml that will be taken, the above is a fallback for all the missing partials, and I can't check right now, but I think even for the template itself...
Railscast: template inheritance!
TL;DR
Rails 3.1, Rails 4, Rails 5 and whatever comes next
app/views/application
The engine searches this path automatically if the view is not found in the controller path.
Rails 3 and prior
app/views/shared
The engine does NOT search this path automatically.
Long story
Rails 3 (and prior version) have no default location for storing shared views.
The unofficial convention is to store shared views in app/views/shared. Wherever you'd end up storing them though, you have to specify the path
# render app/views/shared/menu.html.erb
<%= render :partial => "shared/menu" %>
This suggestion was popularized by Agile Web Development with Rails.
Rails 3.1 introduces an official standard for where to store shared views:
app/views/application
Thanks to this standard, the engine now automatically looks for templates in app/views/application. As a result, you don't have to use the full path anymore.
Those curious can follow here the thought process behind this decision.
Old syntax
# render app/views/application/menu.html.erb
# unless menu.html.erb is found in appp/views/my_controller
<%= render :partial => "menu" %>
New syntax
# render app/views/application/menu.html.erb
# unless menu.html.erb is found in appp/views/my_controller
<%= render partial: "menu" %>
Of course, you can still place your shared views wherever you want and reference them by path
<%= render :partial => "my_own_special_shared_folder/menu" %>
Unless you have a very good reason to do this though, please stick to the new standard and store your shared views in app/views/application.
The Rails View uses app/views/layouts for shared partials like header and footer, but the Ruby on Rails Guide uses app/views/shared in an example. I guess it comes down to personal preference. I would use layouts for high-level stuff like a main nav or a footer, but shared for more narrow controller-level stuff like a form.
I general have a shared folder in my views that contains commonly used partials.
I arrived here in 2021 (rails 6) and got confused by the answers (many different ways).
I asked some senior rails developers what they'd do and they also gave me 2 different answers.
But TL;DR
Create a folder called 'shared' or 'application' inside your views folder (e.g. app/views/shared or app/views/application.
Then simply move the partial there, and access it with either
<%= render partial: 'shared/socials' %>
# or
<%= render partial: 'application/socials' %>
or even simpler
<%= render 'shared/socials' %>
# or
<%= render 'application/socials' %>
It doesn't matter where you put them. You can render any partial at any arbitrary location by providing the file's path to render - it doesn't need to be associated with the controller that's rendering it. I use a directory simply called partials under the view directory, and call partials in it like this:
render :partial => 'partials/mypartial'

Rails Sub-controllers?

I'm pretty new to Rails and have an issue which I can't quite get my
head around as to the architecturally 'correct' way of doing it.
Problem relates to what I kinda call sub-controllers. The scenario is
this:
I have a series of pages, on which is a panel of some form containing
some information (think the user panel on gitHub top right).
So, in my app, I have controllers that generate the data for the pages
and render out the responses which is fine, but when it comes to this
panel, it seems to me that you would want some sort of controller action
dedicated to generating this panel and it's view.
Question is, how do you go about doing this? How do I render a 'sub
controller' from within a view?
I would put the logic in a helper or a module. (http://api.rubyonrails.org/classes/ActionController/Helpers/ClassMethods.html)
Then render partials where you want these things displayed. (http://api.rubyonrails.org/classes/ActionView/Partials.html)
Like Herman said, if it's logic that you need generated after the controller hands off to the view (ie, the Pages controller generates a page view, but you want a customized panel) then put it in a helper. Or, call a separate method in your Pages controller before handing off to the view. Or, if it's a lot of logic, create a Module and stick it in your /lib folder. So you could have a whole Panel module with methods that generate different parts of your Panel and which are called by your controller. But if you want to call these methods from within the view, then you should use a helper instead.
I dont think a module is what is required here, modules are required for shared behaviour across a small subset of your classes.
What I think is required here is the understanding of the inheritance of ApplicationController and also layouts
so, for example, my layout might look like:
<html>
<head><title>Foo</title></head>
<body>
<%= render :partial => (current_user ? "/shared/user_widget_bar" : "/shared/login_bar") %>
<%= yield %>
</body>
</html>
Any code that i want to use for it would go in my ApplicationController since it would be shared across the majority of my app:
before_filter :generate_user_widget
def generate_user_widget
if current_user
#avatar = ...
#unread_messages = ...
end
end
I understand that it might be cleaner for it to belong in a separate controller BUT honestly, unless the code is huge, it doesn't matter and can even still be put inside a module which is then included by ActionController. However it does need to be inside ApplicationController if you consider the scope of it.
If there are more related pages, say for example, you have a Rails app that manages multiple sites and you want shared behaviour across a particular site, try creating a parent controller which has no actions and only private methods, any controllers that need to have access to those methods can inherit off it. That way you can apply before filters to all controllers which inherit off it, saving you the pain of forgetting to add one in your non-parent controllers.
e.g:
class SiteA::SiteAParentController < ApplicationController
before_filter :generate_user_widget
...
end
class SiteA::ProductController < SiteA::SiteAParentController
def index
...
end
end
well, if you really need to call a controller action from the view, you can use components. They were part of the framework, now they only exist as plugins. One such plugin that seems to be well maintained is here: http://github.com/cainlevy/components/tree/master
from its docs:
== Usage
Note that these examples are very simplistic and would be better implemented using Rails partials.
=== Generator
Running script/generator users details will create a UsersComponent with a "details" view. You might then flesh out
the templates like this:
class UsersComponent < Components::Base
def details(user_or_id)
#user = user_or_id.is_a?(User) ? user_or_id : User.find(user_or_id)
render
end
end
=== From ActionController
class UsersController < ApplicationController
def show
return :text => component("users/detail", params[:id])
end
end
=== From ActionView
<%= component "users/detail", #user %>

Resources