Search code examples
ruby-on-railsruby-on-rails-3loopshas-manybelongs-to

Rails how to access belongs_to table without using iteration


This seems like a common but tricky problem, but I wasn't able to find any clue on how to solve this online. I have 2 tables, userprofile and programs. Each userprofile has_many :programs and programs belongs_to :userprofile.

Userprofile.rb

class Userprofile < ActiveRecord::Base
  attr_accessible :user_id, :programs_attributes
  has_many :programs

  accepts_nested_attributes_for :programs

Program.rb

class Program < ActiveRecord::Base
    attr_accessible :userprofile_id, :program_name, :program_role, :program_year

    belongs_to :userprofile
end

My problem is, I want to show the heading ("Programs") only if userprofile.programs is not empty and if each of the program_name, program_role and program_year is not empty.

When I use an iterator, I can do a check on the program_name, program_role, program_year. But the "Program" heading prints multiple times (depending on the number programs that the user has fully filled in).

<% @userprofile.programs.each do |program| %> 
  <% if program.blank? || program.program_name.blank? || program.program_role.blank? || program.program_year.blank? %>
  <% else %>
    <h3>Program </h3> <br />
    <%= program.program_name %><br />
    <%= program.program_role %><br /> 
    <%= program.program_year %>
   <% end %>
 <% end %>

But when I move the heading out of the loop, I am not sure how to do checks:

<% if @userprofile.programs.blank? || @userprofile.programs.program_name.blank? %>
<% else %>
  <h3>Program </h3> <br />
  <% @userprofile.programs.each do |program| %> 

    <%= program.program_name %><br />
    <%= program.program_role %><br /> 
    <%= program.program_year %>

  <% end %>
<% end %>

This will result in a undefined method 'program_name' error. I thought taking the heading out of the iteration and somehow accessing the program_name, program_role, program_year would solve the problem but I'm not sure how to achieve that properly.

I also tried mapping from (how to access fields in belongs_to association in rails)

<%= @program_names = @userprofile.programs.map(&:program_name) %>
<%= @program_years = @userprofile.programs.map(&:program_year) %>
<%= @program_roles = @userprofile.programs.map(&:program_role) %>

  <% if @userprofile.programs.blank? || @program_names.empty? || @program_years.empty? || @program_roles.blank? %>
    <% else %>

      <h3>Program </h3> <br />

       <% @userprofile.programs.each do |program| %> 

         <% if program.program_name.blank? %>
         <% else %>

           <%= program.program_name %><br />
           <%= program.program_role %><br /> 
           <%= program.program_year %>
         <% end %>
       <% end %>
     <% end %>

Which seemed to work, except when all the arrays are, for example: ["","",""] or [nil, nil,nil]. The "Program" heading still shows up when it should be hidden.

Any suggestions would help. Thank you!


Solution

  • For an effecient query, you should do:

    @userprofile.programs.where.not(program_name: nil, program_role: nil, program_year: nil)
    

    The query is not all encompassing though as empty strings will still be included, but you can filter that by adding an extra logic to your model.

    #models/program.rb
    ...
    def displayable?
      program_name.present? && program_role.present? && program_year.present?
    end
    ...
    

    Then in your view, you should be able to do

    <% programs.each do |program| %>
       <% if program.displayable? %>
          <%= program.program_name %><br />
          <%= program.program_role %><br /> 
          <%= program.program_year %>
       <% end %>
    <% end %>
    

    UPDATE

    As @Éric-côté pointed out, you should be able to do:

    scope :displayable_programs, -> { where.not(program_name: nil, program_role: nil, program_year: nil) }
    

    With this approach, in your view, you would do:

    <% programs = @userprofile.programs.displayable_programs %>
    <% if programs.exists? %>
        <h3>Program </h3> <br />
        <% programs.each do |program| %>
           <% if program.displayable? %>
              <%= program.program_name %><br />
              <%= program.program_role %><br /> 
              <%= program.program_year %>
           <% end %>
        <% end %>
    <% end %>
    

    Also following your previous approach, you could use a kind of tracker, e.g,

    <% program_shown = false %>
    <% @userprofile.programs.each do |program| %> 
      <% if program.blank? || program.program_name.blank? || program.program_role.blank? || program.program_year.blank? %>
      <% else %>
        <% unless program_shown %>
          <h3>Program </h3> <br />
        <% program_shown = true %>
        <% end %>
        <%= program.program_name %><br />
        <%= program.program_role %><br /> 
        <%= program.program_year %>
       <% end %>
     <% end %>