Rails: undefined method from a loop with no record? - ruby-on-rails

I'm looping through a table to get some values, and then I'm trying to loop another table to get more items:
My Business table looks like this
id name
1 Business 1
2 Business 2
Business Photo table:
id default_pic business_id
1 blahblah.jpg 1
So if I'm trying to loop:
<% #b.each do |b| %>
<%= b.name %>
<%= b.business_photos.default_pic %>
<% end %>
I get undefined method defaul_pic? I believe because there's no more record after the second loop when its getting Business 2. Whats the rails way to check record association so I don't get this error?
This is how my models look:
class Business < ActiveRecord::Base
has_many :business_photos
end
class BusinessPhoto < ActiveRecord::Base
belongs_to :business
end

<%= b.business_photos.default_pic if defined?(b.business_photos.default_pic) %>
This is a conditional if statement we use each time we want to include a variable & are unsure of whether it's set. I've been looking for something which can prevent the error from showing through a .each (would take out so much logic from views), but I am yet to find one

Related

ActiveRecord has_many where child model is greater than 0

I'm trying to solve a seemingly simple AR problem I'm having. I can't figure out how to return a list of events that have 1 or more attendees, and the docs examples use cache_control which I don't want to use.
I have a simple event model with a has_many relationship:
class Event < ActiveRecord::Base
has_many :attendees
end
and an attendees model that belongs to event
class Attendees < ActiveRecord::Base
belongs_to :event
end
But some events will have 0 attendees. So what's the ActiveRecord way of doing this without using cache_control or writing a custom SQL statement?
It seems like it would be something like:
Event.where(attendees.count > 0)
but I keep getting a column error.
To get only events with attendees you can do: Event.joins(:attendees).uniq.all
Calling all at the end returns an array whereas dropping it will leave you with an activerecord relation.
you can use where method chained with not to do this
Event.where.not(id: Attendee.select(:event_id))
If you do a LEFT INNER JOIN only records with a match in the join table will be returned. So you don't actually need to perform a count.
Event.joins(:attendees)
Will thus only give you events which have attendees. The opposite would be a LEFT OUTER JOIN which returns rows from events even if there is no corresponding rows in attendees.
Counter caches on the other hand can be beneficial for performance since it avoids a COUNT query on the associated table if you want to use a lazy loading association. This can be useful when using fragment caching or if the query to the associated table is expensive.
<%# counter cache avoids query %>
<% cache(event) do %>
<ul>
<%# only perform query if cache is stale %>
<% event.attendees.each do |a| %>
<li><%= link_to a.name, a %></li>
<% end %>
<ul>
<% end if event.attendees.any? %>
http://guides.rubyonrails.org/active_record_querying.html#joining-tables

Testing for lack of associated objects in Ruby on Rails

This follows on from an earlier question as I am bending my brain around Ruby on Rails.
I have items which are displayed on a webpage, depending on whether their status allows the display or not, using a named scope - if the document status ("For Sale", "Sold", "Deleted" etc) has the show_latest_items flag set to 1, it will allow associated items to be displayed on the page :
class Item < ActiveRecord::Base
belongs_to :status
scope :show_latest_items, joins(:status).where(:statuses => {:show_latest_items => ["1"]})
end
class Status < ActiveRecord::Base
has_many :items
end
This is how it is displayed currently
<% latest_items = Items.show_latest_items.last(30) %>
<% latest_items.each do |i| %>
:
<% end %>
So this is all well and good, but I now want to only display the item if it has an associated photo.
class Item < ActiveRecord::Base
has_many :item_photos
end
class ItemPhoto < ActiveRecord::Base
belongs_to :item
end
So in my mind, I should, using the named scope, be able to pull back a list of Items for display, and then filter them using .present? or .any? methods. Curious thing is this:
<% latest_items = Items.show_latest_items.where(:item_photos.any?).last(30) %>
returns an error:
undefined method `any?' for :item_photos:Symbol
Whereas:
<% latest_items = Items.show_latest_items.where(:item_photos.present?).last(30) %>
doesn't error, but it doesn't filter out items with no photos, either.
I've tried various other methods, as well as trying to do custom finders, writing names scopes for photos, but nothing is making a lot of sense. Should I be approaching this from a different angle?
:item_photos.any?
This doesn't work because Ruby's Symbol has no any? method.
.where(:item_photos.present?)
This doesn't do the filtering you're after because you're calling .present? on the Symbol :item_photos which evaluates to true, making the condition really
.where(true)
Try simply
<% latest_items = Items.show_latest_items.joins(:item_photos).last(30) %>
The SQL for this .joins(:item_photos) is going to be an INNER JOIN, causing Item instances with no associated ItemPhoto instances to be omitted from the result.

Activerecord relationship joins

I'm working in Rails and Activerecord and trying to merge some data from related tables together in my view, here are my models:
class Report < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :reports
end
class User < ActiveRecord::Base
has_many :votes
end
Each vote has a user and a report.
In my view I need the following, hopefully as easily as possible:
a total number of votes for each report from all users
a true/false if the user has voted on the particular report
Right now, my basic understanding of ActiveRecord queries only takes me as far as creating a helper with the report and the current user and than querying for the existence of report
Same goes for counting the total number of votes for all users for a report as follows:
Controller
def index
#this is where I need some help to get the related information into a single
#object
#reports = Report.where('...')
end
View
<% #reports.each do |report| %>
<% if(hasVoted(#current_user.id, report.id)) %>
<!-- display the 'has voted html' -->
<% end %>
<% end %>
Helper
def hasVoted(current_user_id, report_id)
if(Vote.exists?(:user_id => current_user_id, :report_id => report_id))
true
else
false
end
end
Hope that gives you some insight into helping...thanks!
Sure.
Firstly, please consider naming your method has_voted? instead of hasVoted. Secondly, consider moving that method in the user model.
#user.rb
def voted_on?(report_id)
votes.where(:report_id => report_id).exists?
end
Your view will then read
<% if current_user.voted_on?(report) %>
...
<% end %>
The other question you had was to find the number of votes a report has received. This is simple too. You could do this in your view inside the loop where you iterate over #reports
<% vote_count = report.votes.size %>
Please keep in mind that his would result in N queries (where N = number of reports). Since you are new to Rails i'm not going to complicate your Reports query in the controller where you fetch you reports to include the vote count (unless you ask me to). But once you are comfortable with what happening in here, thats where you would optimize.

Fetching data using one-many association

I am new to ror. I have 2 tables for group (called 'ab') and sub-group(called 'cd').Each group has several sub-groups.I have defined belongs_to and has_many relationship.
Model ab.rb
class Ab < ActiveRecord::Base
has_many:cds
end
Model cd.rb
class Cd < ActiveRecord::Base
belongs_to :ab
end
ab and cd have 2 columns each called title and Dscr.Do I have to create a join table (ab_cd_join_table)
I want to display a particular group and its sub-groups in a view.
The controller for the view
class DisplayController < ApplicationController
def index
#ab = Ab.find_by_title("XXXXXX")
#cds = #ab.cds
for cd in #cds
logger.info cd.title
end
I am using this in the view.
display view
<%= #ab.title %>
I don't know how to display the title and Dscr of different sub-groups belonging to the group = "XXXXXX"
Thanks in advance
What I think you're asking for is this in the view:
<% #ab.cds.each do |cd| %>
<h1><%= cd.title %></h1>
<p><%= cd.description %></p>
<% end %>
and this in the controller:
#ab = Ab.find_by_title("XXXXXX")
That way you will display all cds for the ab-model matching "XXXXXX".
Update:
For belongs_to and has_many to work the model with belongs_to needs to have a column for the one that has has_many. In this case Cd needs to have a column named ab_id.
rails g migration add_ab_id_to_cds ab_id:integer
cd.ab_id needs to be the id of the corresponding Ab model.
cds = Cd.where(<something>)
cds.each do |cd|
cd.ab_id = #ab.id
cd.save
end
This maybe should be set upon creation of a Cd object, but just to test it out you can do like this.
Do I have to create a join table (ab_cd_join_table)
No in this case you don't need a join table, instead you need to add
ab_id column in cds table.(the foreign key column should be present in
table of model defining belongs_to assiciation)
Displaying subgroup title in view
<% #ab.cds.each |sub_group| %>
<%= sub_group.title -%>
<%= sub_group.description -%>
<%end%>
Also, if you alwas need sub_groups with group then load them in one query by using include option in find like
#ab = Ab.find_by_title("XXXXXX",:include=> :cds)
now you don't need to calculate cds explicitly just use the view code mentioned above

Rails assignment table with third column

I currently have a model for an assignment table in Rails 3, which looks as follows (there are, of course, sale and financeCompany models also):
class SaleFinanceCompany < ActiveRecord::Base
attr_accessible :sale_id, :financeCompany_id, :financedBalance
belongs_to :sale
belongs_to :financeCompany
end
My question is simple: how can I set up the sale/financeCompany models so that I can access the associated financedBalance?
For example, in my view I would like to have:
<% for financeCo in sale.financeCompanies %>
<%= "£" + financeCo.financedBalance + " from "%>
<%= financeCo.name %>
<% end %>
That unfortunately does not work, with the error being the financedBalance part. The only way I could see to set up my finance company model would be with a
has_many :financedBalances, :through => :saleFinanceCompanies
but this will give me several financedBalances for each sale, but I need one (each financedBalance is tied to both a sale and finance company in the assignment table, so doing sale.financedBalances.where etc. would seem unnecessary when I should be able to do sale.financeCompany.financedBalance).
Any suggestions?
Rails treats join tables a bit differently than you might think. From a DBA perspective, your join table is perfectly fine but for Rails true join tables have only referential columns. As soon as you add a new column, Rails likes to treat the join table as a new entity.
(On a personal note, I was frustrated by this at first but I quickly learned it's not a big deal)
So, to fix your problem, you'll need to rename your table, let's say FinanceBalances. Also, let's change financedBalance to amount.
Then, in your Sale.rb file put you associations like so:
has_many :financeBalances
has_many :financeCompanies, :through => :financeBalances
Do the same for FinanceCompany.
And your code will look like:
<% for financeBalance in sale.financeBalances %>
<%= "£" + financeBalance.amount + " from " %>
<%= financeBalance.financeCompany.name %>
<% end %>
If you really really want financeCompany.financedBalance to work, you can define a method in your financeCompany model and write the query that returns what you want.

Resources