简体   繁体   中英

Ruby on Rails 'unless' not working

This is a call for help relating to Ruby on Rails to stop my blood pressure getting to dangerous levels...

Ruby Version: ruby 2.1.0p0 (2013-12-25 revision 44422)

Rails Version: Rails 4.0.3

Essentially I'm creating a web app that lets you get feedback on a design, but I am having trouble with the 'unless' statement when it comes to displaying headings on tests#index page

Controller (this works fine for retrieving the right tests):

def index

    # Gets all tests that have an end date before now
    @completed_tests = Test.where(["end_date < ?", DateTime.now]).order("end_date asc")

    # Gets all tests that have an end date after now        
    @current_tests = Test.where(["end_date > ?", DateTime.now]).order("end_date asc")

end

View (tests#index) (this is the section that is causing me problems)

# This should be if there are no tests for the user, display the Blank state text
<% unless @completed_tests.nil? || @current_tests.nil? %>

        # If there are no completed tests this should not be displayed
        <% unless @completed_tests.nil? %>
            <h3>Completed Tests Section Header</h3>
        <% end %>


        <% @completed_tests.each do |test| %>
            # Do stuff (this works fine)
        <% end %>

        # If there are no current tests this should not be displayed    
        <% unless @current_tests.nil? %>
            <h3>Current Tests Section Header</h3>
        <% end %>

        <% @current_tests.each do |test| %>
            # Do stuff (this works fine)
        <%end%>

<% else %>

# This should be displayed if there are no completed or current tests
<p>Blank state text</p>

<% end %>

The problem in that the headers are showing when there is no tests, eg the completed tests header is showing when there are no completed tests and it shouldn't be, equally if there are no tests I'm getting the completed and current test headers instead of seeing the blank state text.

Other things I've tried:

<% if @current_tests.nil? %>
    <h3>Completed Tests Section Header</h3>
<% end %>

Above: This gets rid of the header regardless of whether there are tests or not

<% if @current_tests %>
    <h3>Completed Tests Section Header</h3>
<% end %>

Above: Shows it regardless of whether there are tests or not (same as original code above)

Any help would be greatly appreciated...

Instead of using obj.nil? , you can use obj.blank? in Rails, which deals with [] as well. In your case, your query may return [] , I think.

<% unless @completed_tests.blank? %>
  <h3>Completed Tests Section Header</h3>
<% end %>

BTW, I hate unless . I have to think for quite a while what actually it means. To me, it is far more difficult to understand than if not .

For the later part,

# This should be displayed if there are no completed or current tests
<p>Blank state text</p>

You need (no computed tests) or (no current tests)

<% if @completed_tests.blank? || @current_tests.blank? %>
  <p>Blank state text</p>
<% end %>

or (no computed tests) and (no computed tests) at all

<% if @completed_tests.blank? && @current_tests.blank? %>
  <p>Blank state text</p>
<% end %>

I'm not sure what you need indeed, because your description is ambiguous.

I +1'd Arie Shaw 's answer, but as I was writing one anyway, I felt I'd just post what I thought:

Your use of .nil? is likely the problem (from another SO post) -

.nil? can be used on any object and is true if the object is nil.

.empty? can be used on strings, arrays and hashes and returns true if:

String length == 0 Array length == 0 Hash length == 0 Running .empty? on something that is nil will throw a NoMethodError.

As mentioned by Arie, .nil returns true if your variable is not set (if it's not in memory). I would say that because you've set the @completed_tests variable (and it will return a result... perhaps blank), your .nil? method will always return false, hence making your logic incoherent:

In Ruby every value is an object. So, nil is an object, too. In fact, it is an instance of the NilClass:

I would say you'll be best to use Arie's blank? answer, or there is also .empty? :

<% unless @completed_tests.empty? || @current_tests.empty? %>

Update

Here's what I would do. The helper is untested, so it may need fixing (specifically the instance_variable_get method):

#app/helpers/application_helper.rb
def tests(test)
  title = test.to_s.titleize
  content = "<h3>#{title} Tests Section Header</h3>"
  instance_variable_get(test).each do |test|
      content + "more html"
  end
  return content
end

<% case true %>
<% when @completed_tests.any? %>
  <%= tests(:completed) %>
<% when @completed_tests.any? %>
  <%= tests(:current) %>
<% else %>
  <p>Blank State Text</p>
<% end %>

I would change a few things here to make the logic easier and perhaps prevent problems in the future:

first, the logic for determining tests belongs in the model. So the following controller code:

# Gets all tests that have an end date before now
@completed_tests = Test.where(["end_date < ?", DateTime.now]).order("end_date asc")

# Gets all tests that have an end date after now        
@current_tests = Test.where(["end_date > ?", DateTime.now]).order("end_date asc")

should be

#test.rb

def completed
  self.end_date.to_i < Time.now.to_i 
end

def current
  self.end_date.to_i > Time.now.to_i
end

#test_controller.rb

def index
  @tests = Test.all
  @completed_tests = @tests.map(&:completed)
  @current_tests = @tests.map(&:current)
end

As Arie Shaw mentioned, unless can be tough to wrap your head around - especially when you're using complex conditions. So I would change this line:

<% unless @completed_tests.nil? || @current_tests.nil? %>

into component parts in partials. something like:

<% if @completed_tests.any? %> #any is the opposite equivalent of `blank` as in Arie Shaw's answer
  <%= render 'completed_tests' %>
<% end %>

<% if @current_tests.any? %> 
  <%= render 'current_tests' %>
<% end %>

<% if @current_tests.empty? && @completed_tests.empty? %>
   <p>Blank state text</p>
<% end %>

#_completed_tests.html.erb

<h3>Completed Tests Section Header</h3>

<% @completed_tests.each do |test| %>
  # Do stuff (this works fine)
<% end %>

#_current_tests.html.erb

<h3>Current Tests Section Header</h3>

<% @current_tests.each do |test| %>
  # Do stuff (this works fine)
<%end%>

This way you're asking one question at a time which is much easier for you to keep track of.

@completed_tests is returning an empty array, now in ruby if you do [].nil? then it will return false , use blank? instead, it is defined in rails, [].blank? returns true . Her's what I have tested in my rails console

2.0.0p247 :085 > a = Product.where("id =?", 10000)
  Product Load (0.5ms)  SELECT `products`.* FROM `products` WHERE (id =10000)
 => #<ActiveRecord::Relation []> 
2.0.0p247 :086 > a.nil?
 => false 
2.0.0p247 :087 > a.blank?
 => true 
2.0.0p247 :088 > [].nil?
 => false 
2.0.0p247 :089 >
2.0.0p247 :089 > [].blank?
 => true 

1 Up for Arie's answer

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM