Letting a template and multiple partials add to the layout - ruby-on-rails

First off, let me say that I am familiar with content_for. It's not really what I'm looking for here.
I want to allow a template and any number of partials to build up, say, a list of JavaScript files I want to load, and pass them up to the layout for it to process and add to the <head> area. I only want to load the files if the page actually needs them, so I'd rather not just put them all into the layout. Nor does it seem like something to be handled by the controller, because these are mainly view-specific changes.
I've tried using content_for to return an array of names, but that doesn't seem to work. It also wouldn't allow for multiple partials to add their own prerequisites to the list. I've also tried using helper functions in the template/partials to add to a list, and then using that list in the layout, but it appears that the layout code is evaluated before the template code.
Any ideas? This isn't JavaScript-specific, by the way; I simply need a way to pass Ruby objects from the template/partials to the layout.
EDIT: Per request, an example. css_import is just a helper I wrote that emulates a CSS #import.
# In the layout
<style type="text/css">
<%- yield(:foobar).each do |foo| -%>
<%= css_import foo %>
<%- end -%>
</style>
# In the template
<%- content_for :foobar do
['layout', 'recipes', 'user']
end -%>
# The actual output from View -> Source
<style type="text/css">
</style>

Maybe you should have a look at what content_for is doing? It just executes the block you assigned to the call and stores it in an instance variable. When calling yield with the right parameter it return the instance variable.
It should be perfectly possible to create two helper methods of your own to accomplish your goal, for example:
def register(key, value)
#registry[key] = value
end
def fetch(key)
#registry[key]
end
You can make these functions as fancy as you like, for example when the registry contains only locations to JavaScript files, you can return HTML JavaScript include tags instead of just the file path.

Related

Parametrized nested erb components in Rails

I'm quite new with Rails, but have React experience. I'd like to manage with erb files sth I could do in React that way:
class MyElement extends Component {
render() {
return <div>
some text
{this.props.myFirstParam + this.props.mySecondParam}
some another text
</div>
}
}
class MyBody extends Component {
render() {
return <body>
<MyElement myFirstParam={1} mySecondParam={2} />
<MyElement myFirstParam={3} mySecondParam={5} />
<MyElement myFirstParam={7} mySecondParam={3} />
</body>
}
}
Of course I know that Rails process files on the server side, but I'd like just to show the idea of component nesting. I'd like to avoid imperative string concatenation in methods too. Is there some good way to do this?
Okay, I found the way. We can do this with render function. I created file named _my_element.erb which looks like this:
<div>
some text
<%= myFirstParam + mySecondParam %>
some another text
</div>
It's located in views folder, which seems to be important.
Next in my_body file I inserted:
<%= render partial: "my_element.erb",
locals: {myFirstParam: 1, mySecondParam: 2} %>
For some reason name of file should begins with _ char, while path in partial parameter should't include it.
But after all, that works.
You've given your own solution already, but let me add a cleaner apporach to what you might be after.
If you've followed the Rails pattern your MyBodyController#show-method sets #my_body and your MyBody has a has_many :my_elements line. You can now type:
<%= render #my_body.my_elements %>
You can now create a 'my_elements/_my_element.html.erb'-file (yep indeed with that underscore) that can contain the HTML for rendering the comment block (if you do this step wrong, Rails will explain you what to do). Since it is a set of records the code will repeated for all comments.
As a more general note: In Rails you typically pass objects (with values, but also methods), instead of just the limited set of separate values needed for rendering that element.
See also: http://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables

rails pass instance variable to layout or local variable

I have a navigation bar (in fact two) and I use a before action in some controllers to fill it's dynamic data (the second bar may not exist in some), I've seen a lot of complains about not passing a lot of instance variables to views, and all of them suggested passing locals in render. I've been wondering using a instance variable to generate these stuff in the main layout is a good idea or not, and if it's not, how should I do this, render seems to overwrite the default view and I use the data in the main layout only
I not sure that I understand well your question. But for some of my menus I use something like that in my layouts:
<%= yield(:menu_top) %>
and I use
content_for :menu_top
to generate content in this area.
For exemple:
<%= content_for :menu_top do %>
<li>my specific content or var</li>
<% end %>
Here is the rails guide for content_for: link

How to avoid embedding javascript in views/show

I have a Rails app that uses javascript (Backbone) to show user specific data on each users profile page /views/users/show.html.erb. I do this by passing <%= #user.id %> as a data parameter to Backbone's fetch function, however, the only way I know how to get the <%= #user.id %> into Backbone's fetch function is by embedding the javascript in each views/users/show.html.erb page, which therefore allows Backbone to load different user specific info for each views/users/show.html.erb page. Although this works, it seems like the wrong way to do it, as I have read that I should not embed javascript like this. Furthermore, I am going to have to do it a lot, because I wish to display a lot of different kinds of data, more than you see below. So the show.html.erb page will be filled with javascript to make the app work the way I wish.
Question: how might I get #user.id into Backbone's fetch function for each user's show page without embedding javascript in the way that I've done. In the comments, someone suggest I use app/assets/javascripts/user.js, but I don't know how to get <%= #user.id %> into that file. #user.id is readily available in show.html.erb
<script type='text/javascript'>
$(document).ready(function() {
app.collections.awardCollection.fetch({ data: $.param({ user_id: <%= #user.id %> }) }).complete(function(){
app.views.awardCollection = new app.Views.awardCollection({ collection : app.collections.awardCollection});
app.views.awardCollection.render()
});
});
</script>
In order to understand how the views works, is that you can add as many extensions to a view as you want, and they will be parsed by the right library.
If you create a view like
my_view.haml.erb
It will be first parsed with ruby (erb), and then with haml, and will end in a html page.
You can create many views for js, usually you want to archive that when you do ajax, so you can end having a js view like:
my_view.js.erb
First ruby will be parsed (all the <% %> tags), that will end as plain text, and then the server will serve the .js file. But that's usually a common task for ajax.
If you have to render a html page where you want to put some js and you need some ruby code on it, what I usually do is to put the data in the html content with a hidden div.
You can put in any view (even on your layout if you want it to be globally available), something like:
<div id="user_id" style="display: none;"><%= #user.id %></div>
And then on a js (or coffeescript or whatever) you can just check the content of that div:
<script type="text/javascript">
var user_id = $("#user_id").html();
</div>
that's really useful when you want to debug or create tests for your js files, since its plain js and won't throw syntax errors.
I see the comment of Luís Ramalho and Gon is a good option, but I recommend use the following approaches:
If the from the variable is not going to change, print it with <%= %> under .js.erb files located in app/assets/javascripts (note that it will be cached until you restart your app)
If you need server variables the best way is to use Ajax
You can define functions on .js files on app/assets/javascripts and call those functions from the views
If you really don't want any Javascript code in the view, you can create the functions on a .js on app/assets/javascripts (corresponding to the view, for order), and use events and/or store the variables in hidden fields (or even use the data attribute from HTML5)

in views with layouts, why do we use "provide" and "yield" instead of passing instant variable?

Am working on Rails Tutorials and I reached the layout part.
It is instructed to use "provide" in the views, and use "yield" in the layout file.
why don't we just use instant variable instead
in code:
view about:
<% #title = 'about' %>
application layout
<title>Website Name | <%= #title %></title>
The book use instead a more complicated syntax, and am sure for a good reason
view about:
<% provide(:title, 'Help') %>
application layout
<title>Website Name | <%= yield(:title) %></title>
Tried both, and both worked fine. But I don't understand why not use the simpler instant variables way?
Instead of provide you can use content_for and pass a block of code. Then yield will be able to execute this block of code - it doesn’t just print a variable.
This is useful because in more complex views you will use another concept know as "helpers". Keep going with the good work and I'm sure things will become clear in the future.
you need to use provide or content_for when you are not able to determine the value of a variable beforehand.
example:
render layout
yield title <-- not yet defined
yield main template
provide title <-- defined here

Conditional content in html.erb

i have a footer as the last element in my application.html.erb page, however for one page in the whole site, i do not want the footer to appear.
Whats the best way to handle this? every solution i come up with is wet (not dry)
Why don't you create a specific layout for this single page? It should be more maintainable than any extra logic.
DRY is not a goal to reach, it's like a conditional warning and like all warnings, you can ignore them if it makes sense.
If you really insist, do this:
<% unless defined? #no_footer %>
your html here
<% end %>
So the footer will disappear only if you set the instance variable in your controller:
#no_footer = true
Another way could be to check params action/controller and put the logic in a helper method.

Resources