method attributes [ajax,jquery,rails4] - ruby-on-rails

I am reading the book Agile web developpment with rails 4.
there is a part where the products' cart is showing only if it is not empty, my question is the function in the view send to the helper only 2 attributes while in the implementation there are 3 parameters.
in the view I have the bellow code, which render to _cart where I have the cart show
<%= hidden_div_if(#cart.line_items.empty?, id: 'cart') do %>
<%= render #cart %>
<% end %>
the helper has:
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)
if condition
attributes["style"] = "display: none"
end
content_tag("div", attributes, &block) end
end
My question is the &block in this case receives id: 'cart' but is it a optional attibute? that why it comes with &. but what about attributes = {}?
I am really not sure how that is happening, could someone explain me a bit?
Thanks!!

The code between and including do and end is the block, and this is the third argument for hidden_div_if, which is simply passed on to content_tag. The & in the definition of hidden_div_if captures the block in your view, whereas the & in the call to content_tag expands it again to pass it along.
The answer here explains this idea nicely with a few examples. I recommend testing everything out yourself in irb to get a feel for it.

Related

How can I yield more than one partial in a block?

I asked a similar question recently, though that was aimed at having multiple blocks as arguments. This problem is a little more immediate.
The problem I have is, I have a helper method which I want to be able to pass content as a block to render. However, if I add more than one partial only the last one in the block is rendered. The methods are below.
def bootrap_panel(title, klass = 'primary', &block)
content_tag(:div, panel_heading(title) + panel_body(&block), class: 'panel panel-' + klass)
end
def panel_body(&block)
content_tag(:div, yield, class: 'panel-body') if block_given?
end
And an example of an issue I am having is here, where only the last partial is being displayed on the page.
=bootrap_panel 'Panels', 'primary' do
- render "dynamic_panels/partials/new"
- render "dynamic_panels/partials/dynamic_panels", dynamic_panels: dynamic_page.dynamic_panels
My first thought is, well I should be yielding the partials to an array or something first, then display that (I am not sure I am able to do that). Secondly, i am using the '-' operator instead of the '=' operator for displaying ruby content in haml. Why?
Pass the block itsef instead of its return value to content_tag:
content_tag(:div, class: 'panel-body', &block)
Other method:
content_tag(:div, capture(&block), class: 'panel-body')

Render ERB as HTML and ERB from a Rails View

I'm making a style guide where I output the code on the right that is displayed on the left.
I know that adding %% escapes ERB
I have written a helper that takes the contents of a block and renders the code in two places one showing the html and I want the other to show the source ERB that created the html.
The problem is I get back HTML where I wanted ERB.
The View Code
<%= display_code do %>
<%= link_to "Button", "/style_guide, class: "btn" %>
<% end %>
The Helper Code
module StyleGuideHelper
def display_code(&block)
content = with_output_buffer(&block)
html = ""
html << content_tag(:div, content, class: "rendered-code")
html << content_tag(:div, escape_erb(content), class: "source-code-preview")
html.html_safe
end
def escape_erb(code)
code = code.gsub("%=", "%%=")
end
end
Expected Result
Button <%= link_to "Button", "/style_guide, class: "btn" %>
Actual Result
Button Button
Cheers
The issue is that this helper runs the block (link_to "Button", ...) -- it never sees the source code inside the block, just its output. You could replace escape_erb with h to capture the resulting HTML, but that won't pop back up to the ERB that generated it.
As I see it, your options are:
Break out examples into partials, then make a helper that a) renders the partial and b) displays the underlying file.
Specify your ERB fragments as strings (heredocs?), pass the string into the helper, and have the helper a) evaluate it via ERB.new(string).result(binding) to render the result and b) display the string.
Make the helper determine what part of the view invoked it, then parse the .erb well enough to find the block. Catch is, the precise format of what you see in callers is subject to change without notice due to the way views are compiled.
Make a helper that uses crazy metaprogramming juju to evaluate the block in both an ERB context as well as your own special context that intercepts the code being evaluated and turns it back into markup.
...sorted in approximate order of complexity and odds of success.
This code below will allow you to retrieve the code for a given block.
class ERBSource
ERB = ::ActionView::Template::Handlers::ERB
def self.for(block)
new(block).source
end
attr_reader :block, :file, :line_number
def initialize(block)
#block = block
#file, #line_number = *block.source_location
end
def source
lines = File.readlines(file)
relevant_lines = lines[(line_number - 1)..-1] || []
extract_first_expression(relevant_lines)
end
private
def extract_first_expression(lines)
code = lines.slice[0,1].join # add the first two lines so it has to iterate less
lines.each do |line|
code << line
return code if correct_syntax?(compile_erb(code))
end
raise SyntaxError, "unexpected $end"
end
def correct_syntax?(code)
stderr = $stderr
$stderr.reopen(IO::NULL)
RubyVM::InstructionSequence.compile(code)
$stderr.reopen(stderr)
true
rescue Exception
$stderr.reopen(stderr)
false
end
def compile_erb(code)
ERB.erb_implementation.new(
code,
:escape => false,
:trim => (ERB.erb_trim_mode == "-")
).src
end
end
This is what the helper looks like
module StyleGuideHelper
def render_example(name, &block)
code = ERBSource.for(block)
content_tag(:h2, name) +
content_tag(:div, &block) +
content_tag(:pre, content_tag(:code, code))
end
end

Getting text to show up if a condition is true

I'm trying to get the text "Tags:" to show up only if tags are present, so I did the following in my view:
<% if #tags.present? %>
<%= puts "Tags:"%>
<% end %>
Which doesn't work... I'm a beginner, and have no idea what I'm doing wrong.
Thanks
EDIT:
A tag belongs to an Article.
Tags is defined in my Article model as:
def tag_tokens
self.tags.collect{|t| t.name}.join(", ")
end
def tag_tokens=(tags_delimited)
tags_delimited.split(",").each do |string|
self.article_tags.build(:tag => Tag.find_or_create_by_name(string.strip.downcase))
end
end
I'm trying to make it so that when an article has tags the word "Tags:" shows up before the list of tags, and when an article doesn't have any tags, the word "Tags:" doesn't show up.
Right now <% if #tags.nil %> just causes "Tags:" to show up on every post.
You don't use puts in views -- puts causes the text to go to your console. This will fix it:
<% if #tags.present? %>
<%= "Tags:"%>
<% end %>
You also don't need to use .present? by the sound of it. If you only want to see if it's been set, you should use .nil? instead. You can also condense this down to a single line.
<%= "Tags:" unless #tags.nil? %>
UPDATE: It looks like the tag_tokens method is broken for you in both the getter and setter. Your setter isn't actually saving anything by the looks of it (.build returns a new object, you need to save it). Your getter is also referencing tags, instead of article_tags which is what you're trying to save by the looks of it. Changing it to this should work for saving:
self.article_tags.build(:tag => Tag.find_or_create_by_name(string.strip.downcase)).save
This is assuming that you have a line that is something like:
has_many :article_tags
has_many :tags, through: :article_tags
Which I'm assuming you do based on your setter.
I assume this is a many-to-many relationship, but it looks like you're using has_many :through, rather than has_and_belongs_to_many. Is there a reason for this? If you're using has_and_belongs_to_many you should be able to do this:
has_and_belongs_to_many :tags
def tag_tokens=(tags_delimited)
self.tags = []
tags_delimited.split(",").each do |string|
self.tags << Tag.find_or_create_by_name(name: string)
end
end
If you do that, you should not have an ArticleTags model at all, and you should have a table called articles_tags with no primary column, and an article_id and tag_id column.
Update 2:
You're not setting #tags to anything, which is why it's always nil. #tags is a variable, which needs to be set to have a value just like #articles is being set in your controller's index method. Regardless, since this is for an index method, you wouldn't want it to be a single instance variable regardless. You should be accessing your tag_tokens method for that particular instance. app/views/articles/index.html.erb lines 53-55 should be changed to this:
<%= "Tags:" if article.tags.any? %>
Check the answer by sgrif, it contains a lot of good points. To just answer your main question:
In erb (the "language" used for view templates in Rails) you can use <%= ... %> to interpolate the result of some Ruby code into your view template.
When you are doing:
<%= puts "Tags:" %>
the following happens:
Ruby evaluates/executes your code: "Tags: " is printed to STDOUT and nil is returned since a call to puts alsways returns nil
erb interpolates the result into your template, the result is nil, which shows up as "nothing"
To fix it, just use:
<% if #tags.present? %>
<%= "Tags:"%>
<% end %>
or, since you are not doing anything in Ruby, you can just use:
<% if #tags.present? %>
Tags:
<% end %>
What has #tags been defined as? Where do you want to check if it is present?
Do you want if #tags.nil?

I can't figure out helper in Rails

Hey guys
I'm new to rails. I made this small test code for learning helper in rails:
apps/helpers/home_helper.rb
module HomeHelper
def show(var)
yield var
end
end
apps/views/home/index.html.rb
<%= show('hello world')%>
When I navigate to the url localhost:3000/home/index I got nothing in the html source
What did I do wrong?
There are few things to note here:
module HomeHelper
def show(var)
yield var
end
end
Firstly you're using yield which will pass control to the block given to the method. However, you then call the method without a block:
<%= show('hello world') %>
If you would have had a block it would have looked something like this:
<%= show('hello world') do |v| %>
<%= v %>
<% end %>
This would have output 'hello world' as you expected.
Most like you meant:
module HomeHelper
def show(var)
var
end
end
This returns the value you're passing in and will output it to the response stream.
While block helpers can often to be useful for drying up your code most of the time you want a partial with a layout.
why are you doing yield? Go with simple return:
def show(var)
var
end
Remove the yield. yield is meant for blocks - did you mean return? (it is optional).
Another (arguably better) option is to set the display text in your controller.
home_controller.rb
HomeController < ActionController
# Other controller code...
def index
#text = "Hello, world!"
end
end
index.html.erb
<%= #text %>

Dealing with nil in views (ie nil author in #post.author.name)

I want to show a post author's name; <% #post.author.name %> works unless author is nil. So I either use unless #post.author.nil? or add a author_name method that checks for nil as in <% #post.author_name %>. The latter I try to avoid.
The problem is that I may need to add/remove words depending on whether there is a value or not. For instance, "Posted on 1/2/3 by " would be the content if I simply display nil. I need to remove the " by " if author is nil.
Null object pattern is one way to avoid this. In your class:
def author
super || build_author
end
This way you will get an empty author no matter what. However, since you don't actually want to have an empty object sometimes when you do expect nil, you can use presenter of some kind.
class PostPresenter
def initialize(post)
#post = post
end
def post_author
(#post.author && #post.author.name) || 'Anonymous'
end
end
Another way is using try, as in #post.author.try(:name), if you can get used to that.
You can use try:
<%= #post.author.try(:name) %>
It will attempt to call the name method on #post.author if it is non-nil. Otherwise it will return nil, and no exception will be raised.
Answer to your second question: In principle there is nothing wrong with the following:
<% if #post.author %>
written by <%= #post.author.name %>
<% end %>
or
<%= "written by #{#post.author.name}" if #post.author %>
But if this is a recurring pattern, you might want to write a helper method for it.
# app/helpers/authors_helper.rb or app/helpers/people_helper.rb
class AuthorsHelper
def written_by(author)
"written by #{author.name}" if author
end
end
# in your views
<%= written_by(#post.author) %>
Write a method which accepts any variable and checks to see if it is nuil first, and if it is not displays it. Then you only have to write one method.
I found your question interesting as I have often come across similar situations, so I thought I'd try out making my first Rails plugin.
I'm afraid I haven't put in any tests yet but you can try it out http://github.com/reubenmallaby/acts_as_nothing (I'm using Ruby 1.9.1 so let me know if you get any problems in the comments or on Github!)

Resources