Parametrized nested erb components in Rails - ruby-on-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

Related

How to SSR nested components or slotted content with vueonrails?

I am trying to add Vue.js components to a Ruby-on-Rails app with SSR support by using Hypernova and vueonrails helpers.
I am able to render a registered component with Hypernova from a view (.html.erb) by using this helper:
<%= render_vue_component('HelloWorld', message: 'Hello World from SSR!') %>
Let's say my HelloWorld component has a <slot> in its template:
<template>
<div class="helloworld">
<h1>{{ message }}</div>
<slot></slot>
</div>
</template>
I want to replicate something like the following in the .html.erb using render_vue_component:
<hello-world message="Hello World from SSR!">
<hello-world>SSR Nested component</hello-world>
</hello-world>
How could I pass child elements to the render_vue_component helper so they get rendered by Hypernova?
Well, I analyzed the repo and by looking at the it, I think you should use the render_vue_component only for rendering the root component, like the main.js when you start a project with #vue/cli:
<%= render_vue_component('App', { router }) %>
I came to this conclusion by analyzing the vueonrails source code.
If you look to the render_vue_component function, it only takes the identifier and a data object, which internally calls render_react_component.
By this signature, it simply don't fit with Vue's render function signatures, which are (element, children) or (element, attributes, children).
Maybe you find a better support by opening an issue on the repo.
But I'd think twice before using this project because there isn't any documentation so far and checking the website, seems like they are more focused in selling books about the project than creating a documentation.

Variable dustjs partial name

I am setting up some heavy split testing using dust.js on my single page app.
The base template looks like (simplified):
{>"layouts/master" /}
{<body}
<div id="container">
{?home}{>homeWelcome/}{/home}
</div>
{/body}
What I'm trying to do is have a folder containing N versions of the homeWelcome partial, and send N through the model to select the right template, like so :
{<body}
<div id="container">
{?home}{>/splits/homeWelcome_{partialNumber}/}{/home}
</div>
{/body}
But it (unsurprisingly) doesn't compile.
I could send params to the one homeWelcome template, and have all my splits in there but some are radically different from the others and it'd make for one hell of a long file.
In addition to that, I want to be able to add/remove partials in the splits directory dynamically (partialNumber is a rand from 1 to the number of files in the dir).
Any ideas how to achieve that?
Just add double quotes around the partial name and dust will happily parse the string before including a partial.
Note that partial names don't necessarily relate to folder structure, but I'm assuming you are compiling your templates with the appropriate names.

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)

Raw Mako template included in another in Pylons

I'm trying to include a raw Mako template to make it appear in a textarea with Pylons.
In Pylons, I know I can include one Mako template in another like this:
<html>
<body>
Here is some HTML. Now, I want to include a template.
<%include file="${c.a_mako_template}" />
</body>
</html>
Is there a way that I can do the same thing, but have the template appear as raw markup rather than passing through Mako renderer? I'm imagining something like:
<%include file="${c.a_mako_template}" render="false" />
If that sort of thing is impossible, is the best route to write a Pylons controller that does the inclusion at a later stage?
Could I somehow wrap the file to be included in <%text> tags on the fly, perhaps using <% def>?
I've figured out a sort of workable solution. It still has one rough bit, but it works.
<%
path = '/path/to/file/'
f = open(path + c.sourcefile, 'r')
text_to_edit = f.read()
f.close()
%>
<textarea id="code">
${text_to_edit}
</textarea>
The first part is just a chunk of Python embedded in the template. It opens the file and extracts the text, thereby bypassing Mako.
The rough bit is the hardcoded path. I'll have to pass that as c.path, or something like that.

Letting a template and multiple partials add to the layout

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.

Resources