Using Rails 3. I have 2 options for my view:
OPTION A:
<% #items.each do |item| %>
...
<% end %>
...
<script>
var items = [
<% #items.each do |item| %>
['<%= item.name %>', <%= item.lat %>, <%= item.lng %>],
<% end %>
];
</script>
OPTION B:
<% #items.each do |item| %>
...
<% content_for :items_array do %>
['<%= item.name %>', <%= item.lat %>, <%= item.lng %>],
<% end %>
<% end %>
...
<script>
var items = [<%= yield :items_array %>];
</script>
At the moment, I have chosen OPTION B, but I still think that it is not neat at all. Any better way to do this? I refused to choose OPTION A because I don't want it to loop twice in the same view because of performance and DRY issues, correct me if I am wrong.
Thanks.
If it were my project I would probably move this problem to the javascript side. Put the lat and long into data- attributes somewhere in the item tags and use unobtrusive scripting to read them. (If you want an example let me know.)
If that's not possible, I personally would prefer option A, first of all for readability (it's easier to understand, especially if you're not familiar with content_for) and second because it's easier to refactor, we could move collecting the data into a method on the model or maybe into a helper. DRY doesn't really apply here because even though you're looping through the same dataset, you're doing very different things with it.
That said, I wasn't quite sure about the performance implications so I ran a little test. I took 500 client records in one of my apps and used the option A to render a list of links followed by mapping their data into a content_for block and benchmarked it using Ruby's inbuilt Benchmark module (it's in haml instead of erb but I'm sure it's not too hard to read.)
- bench = Benchmark.measure do
%ul#client-list
- #clients.each do |client|
%li
= link_to client.company, client
- content_for :foobar do
= "['#{j client.name}', '#{j client.company}', '#{j client.email}'],"
= javascript_tag do
var nearbys = [#{yield :foobar}];
%p= bench.to_s
The result, about 370 miliseconds
0.370000 0.000000 0.370000 ( 0.397476)
Then I did the same with two loops (one each one map):
- bench = Benchmark.measure do
%ul#client-list
- #clients.each do |client|
%li
= link_to client.company, client
= javascript_tag do
= "var nearbys = [#{ #clients.map { |client| "['#{j client.name}', '#{j client.company}', '#{j client.email}']," }}]"
%p= bench.to_s
The results, about 80 miliseconds:
0.080000 0.000000 0.080000 ( 0.077940)
You may want to check the results yourself, but it seems like the content_for method is not a very efficient means to solve this problem.
edit: just for reference, this was tested using ree 1.8.7 and rails 3.2.6
edit2:
An example of how to rewrite this without using inline javascript. I'd first move all the required parameters into data- attributes, for example in the view:
<% #items.each do |item| %>
<div class="item" data-name="<%= item.name %>"
data-lat="<%= item.lat %>"
data-long="<%= item.long %>">
<%= item.name %>
</div>
<% end %>
(You could also take the name from inside the div, but putting everything in data-attributes is more consistent.)
Then in you javascript you could map the data as follows (assuming jquery):
var nearbys = $('.item').map(function() {
return [ $(this).data("name"), $(this).data("lat"), $(this).data("long") ];
});
This way you can keep all your javascript neatly in a separate file and collecting the data is done client-side.
it's very simple just use to_json it will do all the escaping for you ;)
So your code could look like this:
<script>
var items = <%= #items.map{|o| [o.name, o.lat, o.lng]}.to_json.html_safe %>;
</script>
(the html_safe marks the string as safe, avoiding double escaping)
If you have objects that are more complex than just arrays of strings and numbers you should use jQuery's $.parseJSON(json) so it would look like:
<script>
var items = $.parseJSON('<%= #items.map{|o| [o.name, o.lat, o.lng]}.to_json.html_safe %>');
</script>
Enjoy!
All of this code should go into a function in the helper file for the Model. That is what the helpers are for, right?
The views should have minimum Ruby code hard-wired into them. So, use helpers.
Here are some links to get you more insight into helpers:
using-helper-methods-in-ruby-on-rails
what-belongs-in-a-helper-method
Related
I have an instance variable #tally_property, and if there are photos on that object I would like to cycle through the photos and show them.
So my code snippet looks like this:
<% if #tally_property.photos.present? %>
<% #tally_property.photos.each_with_index do |photo, index| %>
The issue is that based on the above, if #tally_property is nil, then the entire first line throws an error.
So is there a 'nil' check I can do that isn't bulky, i.e. I don't want to do if #tally_property.nil?, on both the primary object and the association, and is elegant and ruby & rails-esque?
I would use the safe navigation operator (&.) and write something like this:
<% #tally_property&.photos&.each_with_index do |photo, index| %>
...
<% end %>
In Ruby 2.3.0+ you can use the safe navigation operator:
#tally_property&.photos
ActiveSupport has a .try method that can be used to the same end in older versions of ruby:
#tally_property.try(:photos)
You can add a simple conditional to be able to safely iterate through the collection:
<% (#tally_property.try(:photos)||[]).each_with_index do |photo, index| %>
<% end %>
Rails 4 adds ActiveRecord::Relation#none and a change in behaviour so that associations always return a ActiveRecord::Relation. So its perfectly acceptable to write:
<% #tally_property.try(:photos).try(:each_with_index) do |photo, index| %>
<% end %>
After upgrading your app. Or you can use a partial and render:
<%= render partial: 'photos', collection: #tally_property.photos if #tally_property %>
Which removes the need for writing the iteration.
Use && (or and, they each have their sweetspot).
Taking it out of Erb for a moment, I would generally write something like this:
if #tally_property and #tally_property.photos.present?
Depending on photos I might use:
if #tally_property and #tally_property.photos
or perhaps:
if #tally_property and not #tally_property.photos.empty?
Sometimes I'll use a temporary variable:
if (photos = #tally_property && #tally_property.photos)
photos.each #…
That kind of thing.
I would recommend this episode of Ruby Tapas, And/Or for a longer (but still quick) look at it.
One more way, just select all photos connected to this tally_property:
example how it might be:
Photo.joins(:tally_property).each_with_index do |photo, index|
In rails, I often run into the situation where inside the views I'll do something like
<% if #some_condition_previusly_established_in_a_controller %>
<div class="one">123</div>
<% else %>
<div class="two">something else</div>
<% end %>
It looks a bit cluttery. Is this an acceptable way of working with views or not?
Unless you can think of a way to re-write this as a helper method, you're basically stuck with it looking kind of ugly. That's just how ERB is, as it was intended to be a minimal way of injecting Ruby into an otherwise plain-text template, not as something necessarily streamlined or elegant.
The good news is a syntax-highlighting editor will usually make your <% ... %> ERB blocks look visually different from your HTML so that can dramatically improve readability.
It's also why other representations like HAML have been created where that syntax is a lot less cluttered:
- if some_condition_previusly_established_in_a_controller
.one 123
- else
.two something else
For one or two such conditional logic in your views, I guess its fine but when your code gets bigger and you have multiple if..else..end and looks "cluttery", I think you should look at implementing "Presenter Pattern" which greatly cleans up your views by separating your logic to Presenters.
Here is a great tutorial I followed from Ryan Bates in his Rails Casts series on "Presenter Patterns from scratch". http://railscasts.com/episodes/287-presenters-from-scratch.
Have you tried?
<% #some_condition_previusly_established_in_a_controller ? <div class="one">123</div> : <div class="two">something else</div> %>
If your view contains lots of tags and HTML elements, you can put them into partials and logic into model
View:
<%= render :partial => #model.status %>
<%= render :partial => "file/path/#{#model.status}" %> # if your partial is in some different folder
If your status is one, then it would render the file _one.html.erb
If it is two, then it would render the file _two.html.erb automatically.
Model:
def status
if #some_condition
"one"
else
"two"
end
end
Yes, that is the standard (and yes, it looks cluttery).
If you're looking for a possibly cleaner alternative, check out: Conditional tag wrapping in Rails / ERB
You can always move the logic to the controller and leave the view clean(er).
Controller:
if #some_condition
#div_class = :one
#div_content = 123
else
#div_class = :two
#div_content = 'something else'
end
View:
<div class="<%= #div_class %>"><%= #div_content %></div>
Or using a helper:
<%= content_tag :div, #div_content, class: #div_class %>
How to add "0," after each loop in Ruby (At the data: part of the series)
So first loop would
show only the value
next loop 0,value
next loop 0,0,value
next loop 0,0,0,value
etc..
series: [
<% #prot = ProjectTask.where("project_id = ? AND taskType = ?" ,#project.id, "Pre-Sales")%>
<% #prot.each do |prt| %>
<% hoursSum = 0 %>
{
name: '<%= prt.task_name%>',
data: [(here after each loop of #prot i want to add "0," here)<% #taskHours = Effort.where(:project_task_id => prt.id) %>
<% #taskHours.each do |th| %>
<% hoursSum = hoursSum + th.hours %>
<% end %>
<%= hoursSum%>
]
},<% end %>
<% #prot.each_with_index do |index, prt| %>
...
<%= "0," * index %>
...
<% end %>
This logic seems too complex to be in a view - I would migrate it to a helper function that builds up the data array and call that instead.
I think you'll find it a lot easier to do what you want then without having to deal with all the clutter of the erb tags etc. There's lots of ways you could do it - Yossi's suggestion of using each_with_index is a perfectly good one.
Two little things though - I would advise against shortened names for stuff like #prot - just call it #project_task. It's more readable and you can guarantee you call it the same thing throughout your code (instead of having some places where you call it #prot, others where it's #ptask etc) which will save you more time than you lose typing a longer name, I promise you.
Also - you use camelCase for some of your variables - I would advise sticking with the Ruby convention of snake_case.
I have a rails template (.rhtml file) generating a Javascript object. It looks something like the following:
var volumes = {
<% for volume in #volumes %>
<%= volume.id %> : <%= volume.data %>
<%= ',' unless volume === #volumes.last %>
<% end %>
};
Note the unless statement modifier to suppress printing the comma after the last element (to satisfy Internet Explorer, which incredibly doesn't support trailing commas in JSON properties declarations).
This appears to work, but as a matter of style, do people think it is reasonable to rely on <%= value unless condition %> in the template generating an appropriate render call?
I don't see why not, but generally if you find yourself conditionalizing a comma on the last member, you probably want to use join instead:
<%= #volumes.map {|v| "#{v.id} : #{v.data}"}.join "," %>
If you would like to contruct JSON (and BTW you are constructing JavaScript Object not Array) then I suggest to use to_json method:
var volumes = <%= #volumes.inject({}){|h,v| h.merge(v.id=>v.data)}.to_json %>;
or
var volumes = <%= Hash[*#volumes.map{|v| [v.id, v.data]}.flatten].to_json %>;
Even better would be to move Ruby Hash construction to model as it is too complex for view.
class Volume
def self.to_hash(volumes)
Hash[*volumes.map{|v| [v.id, v.data]}.flatten]
end
end
and then in view you can put much simpler code:
var volumes = <%= Volume.to_hash(#volumes).to_json %>;
Or even:
<%= #volumes.map { |v| "#{v.id} : #{v.data}"}.to_sentence -%>
To get "a: something, b: something else, c: anything, and d: another thing."
I have the need to display a nested set structure in HTML. I am doing it with the following partial:
<ul<%= ' id="tree"' if depth == 0 %>>
<% items.each do |item| %>
<li id="node_<%= item.id %>"><a><%= item.name %></a>
<% if item.has_children? %>
<%= render :partial => 'tree_level', :locals => {:items => item.children, :depth => depth + 1} %>
<% end %>
</li>
<% end %>
</ul>
Is this the best place to have the code? I "feel" like there should be a to_html method on the object which dumps the entire tree structure for me, though this works.
I am not sure whether it is best practice but I used similar code for rendering project tree.
Faster alternative is to create helper method doing the same job (recursively traversing tree and adding partial strings into result string). It is a little bit PHP style :( but for such a small amount of HTML is it OK, I guess :)
Helper looks like:
def render_node(node)
res = "<ul>"
...
node.items.each {|n| res << render_node(n)}
...
res << "</ul>"
res
end
Then it is used like this:
<%=render_node ProjectTree.new%>
Well, you should realize there's a (small) overhead for using partials, so if performance is an issue, you may not want to use them this much. Otherwise I see little problem with using this.
However, you might want to use the collection-variant of partials (see "Rendering a collection of partials" on this API page, it could clean up your code a bit.