Ruby : Access array object by index vs iterator - ruby-on-rails

I'm pretty new to Ruby and the Rails framework. My background is primarily Java. Anyhow, I'm facing a weird situation. I have a method in one of my models that returns associated models. The association is as follows. A has_many Bs, and B belongs to A (i.e. one-to-many)
class ModelA < ActiveRecord::Base
has_many :model_bs
def get_bs
ModelB.where(:a_id => id)
end
end
class ModelB < ActiveRecord::Base
belongs_to :model_a
end
In my view, if I try to access the records (models) in the result set, I'm able to call its properties without any issue (Figure A). Life is good.
Figure A:
<% bs = a.get_bs %>
<% bs.each do |b| %>
<%= b.some_prop %>
<% end %>
But if I try to access the models by index, I get an error saying that I can't call a method on a nil object (Figure B & C).
Figure B:
<% bs = a.get_bs %>
<%= bs[0].some_prop) %>
Or even..
Figure C:
<% bs = a.get_bs %>
<%= bs[0].first %>
Does not work. I know it's user error (me). I've looked at the documentation for accessing objects from a collection (in this case, I believe it's a Ruby array). I've also searched here on StackOverflow. I'm still left scratching my head. I haven't quite found a similar thread.

You are wrong, it is not Array, it is an ActiveRecord::Relation class. You can transform it to an array with .to_a, if you really need it. I've checked, you can use [] operator to access item by index: ModelA.where("created_at = created_at")[0].name, so I think the problem is somewhere else, maybe in your condition.
Check the documentation.
But anyway, you shouldn't use the relationship like this. Use has_many and belongs_to to indicate relationship between models. Like this:
class ModelA < ActiveRecord::Base
has_many :ModelB
end
class ModelB < ActiveRecord::Base
belongs_to :ModelA
end

I found a solution. It's not pretty but it'll due for now. With a little help from someone on LinkedIn, I discovered that using the .try method on my model while attempting to access an attribute, I'm able to retrieve the value without the null pointer exception.
Example:
<% bs = a.get_bs.to_a%>
<%= bs[0].try(:some_attr) %>
It's not clear to me as why I need to use the .try method. I mean, I know what the method is for. It's a convenience method for checking nil values and allowing the page to render without throwing an exception. But it's obvious that my model is not null and it has data. So why am I only able to access its attributes with the .try method? Honestly, I think this could be a bug in Rails.
I think what I'll end up doing is create a helper method that utilizes the .try method. That way, I'm not calling .try all over my views.

Why not using this?
def get_bs
ModelB.find_all_by_a_id(id)
end

Related

Showing the email of the person to whom I sent a comment

I use the following code to show the email of the person to whom I sent a comment.
Controller:
#to_user = User.where(id: #comment.to_id)
View:
<%= #to_user.email %>
Only it generates an error: undefined method `email' for #<User::ActiveRecord_Relation:0x007feb364b8218>
Showing "from" user works well:
<%= #comment.user.email %>
Not sure what I am doing wrong. Help would be much appreciated.
To retrieve a single object, you use find:
#to_user = User.find(#comment.to_id)
But it's more idiomatic to define a belongs_to association in your Comment model, e.g.:
class Comment < ActiveRecord::Base
belongs_to :to_user, class_name: 'User', foreign_key: 'to_id'
# ...
end
This allows you to simply write:
<%= #comment.to_user.email %>
To avoid confusion, you might want to rename the exising user to from_user. Or maybe use more descriptive names like sender and receiver.
And if you do so, it would be a good idea to also rename the columns from user_id and to_id to from_user_id and to_user_id (or sender_id and receiver_id).
.where returns an ActiveRecord_Relation, which you can think of like an array of records (actually, the records aren't loaded yet, but as soon as you call a method like .each or .to_a it evaluates the query and turns it into an array).
What you are looking for is either
.find (User.find(#comment.to_id)) - this will raise an error if the user is not found
.find_by (User.find_by(id: #comment.to_id)) - this will return nil if the user is not found
You could also just call .first on the .where result - as often is the case in Ruby/Rails, there are many ways to do the same thing.

Accessing the associated join model when iterating through a has_many :through association

I have a feeling this is a pretty basic question, but for some reason I'm stumped by it (Rails newbie) and can't seem to find the answer (which may be I'm not searching properly).
So I have a basic has_many :through relationship like this:
class User < ApplicationRecord
has_many :contacts, through :user_contacts
class Contact < ApplicationRecord
has_many :users, through :user_contacts
In users/show.html.erb I'm iterating through a single user's contacts, like:
<% #user.contacts.each do |c| %>
<%= c.name %>
<% end %>
Now inside of that each loop, I want to access the user_contact join model that's associated with the given user and contact in order to display the created_at timestamp that indicates when the user <--> contact relationship was made.
I know I could just do a UserContact.find call to look up the model in the database by the user_id and contact_id but somehow this feels superfluous. If I understand correctly how this works (it's entirely possible I don't) the user_contact model should have already been loaded when I loaded the given user and its contacts from the database already. I just don't know how to properly access the correct model. Can someone help with the correct syntax?
Actually the join model will not have been loaded yet: ActiveRecord takes the through specification to build its SQL JOIN statements for querying the correct Contact records but effectively will only instantiate those.
Assuming you have a UserContact model, you could do sth like this:
#user.user_contacts.includes(:contact).find_each do |uc|
# now you can access both join model and contact without additional queries to the DB
end
If you want to keep things readable without cluttering your code with uc.contact.something, you can set up delegations inside the UserContact model that delegate some properties to contact or user respectively. For example this
class UserContact < ActiveRecord::Base
belongs_to :user
belongs_to :contact
delegate :name, to: :contact, prefix: true
end
would allow you to write
uc.contact_name
First of all, the has_many :things, through: :other_things clause is going to look for the other_things relationship to find :things.
Think of it as a method call of sorts with magic built in to make it performant in SQL queries. So by using a through clause you're more or less doing something like:
def contacts
user_contacts.map { |user_contact| user_contact.contacts }.flatten
end
The context of the user_contacts is completely lost.
Since it looks like user_contacts is a one-to-one join. It would be easier to do something like this:
<% #user.user_contacts.each do |user_contact| %>
<%= user_contact.contact.name %>
<% end %>
Also since you're new to Rails it's worth mentioning that to load those records without an N+1 query you can do something like this in your controller:
#user = User.includes(user_contacts: [:contacts]).find(params[:id])
Use .joins and .select in this way:
#contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')
Now, inside #contacts.each do |contact| loop, you can call contact.user_contact_attribute_name.
It looks weird because contact doesn't have that user_contact_attribute_name, only UserContact does, but the .select portion of the query will make that magically available to you on each contact instance.
The contacts.* portion is what tells the query to make all contact's attributes available as well.

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.

Rails Method not available in View

I get this strange error. I have defined a method for my model
class Rating < ActiveRecord::Base
[...]
belongs_to :user
belongs_to :movie
[...]
def to_text
texto = case self.grade
when 0..1 then "horrible"
when 2..3 then "bad"
when 4..5 then "not bad"
when 6..7 then "good"
when 8..9 then "very good"
when 10 then "master piece"
end
end
end
Then, on my controller, I define this instance:
#current_user_rating=#movie.ratings.where(:user_id => current_user)
And it does find it, so it works. But then, when I call the method, or a property like
<%= #current_user_rating.grade %>
<%= #current_user_rating.to_text %>
I get this error
undefined method `grade' for []:ActiveRecord::Relation
undefined method `to_text' for []:ActiveRecord::Relation
Why the variable not behaving as an instance with appropriate attributes and methods but as a Relation?
It does work on the Console, but not on the server...
since a movie has multiple ratings, #current_user_rating is a collection of them, even if there is one. you should be able to get rid of the errors by calling your methods like this:
<% #current_user_rating.each do |rating| %>
<%= rating.grade %>
<%= rating.to_text %>
<% end %>
When you call where it doesn't actually query the database, it creates an object which can be converted and run on the database. This is useful for further modifying the query before it is run.
Typically a further call to first or all is added to actually execute the query and get the result. (Note that this might be done automatically if you call where in the rails console, so that the result can be printed)
In your case, it looks like a user is expected to rate a movie no more than once, so the following seems appropriate:
#current_user_rating=#movie.ratings.where(:user_id => current_user).first
EDIT: Actually I think GSto is correct, what I wrote above does not look like the reason for your failure. The error message is actually complaining that the methods you are trying to call aren't valid to be called on an Array of objects. In this case, first is simply selecting the first result and ignoring any others.
It would be good to double-check if you have any movies for which a single user has multiple ratings. In fact, I'd recommend adding a validation to ensure that this is not allowed, if it is not intended.

has_many through and partials

I have a User model, a Post model, and an Interest model.
User has_many posts through interests
User has_many interests
Post has_many users through interests
Post has_many interests
Interest belongs to Post
Interest belongs to User
Application_Controller is as follows:
class ApplicationController < ActionController::Base
before_filter :login_from_cookie
before_filter :find_user_interests
helper :all # include all helpers, all the time
session :session_key => '_blah_session'
include AuthenticatedSystem
def find_user_interests
#user_interests = current_user ? current_user.interests : []
true
end
end
Application.html.erb has as follows:
<%= render :partial => "users/interests", :object => #user_interests %>
_interests.html.erb partial is as follows:
ul
<% unless current_user.nil? then -%>
<% #user_interests.each do |interest| -%>
li<%= interest.post.title %>/li
<% end %>
<% end -%>
/ul
Given all this when I at localhost:3000/posts/1 my partial shows up fine, but when in localhost:3000/posts I get an error undefined method 'title' for nil:NilClass thus an error in the line li<%= interest.post.title %>/li shown above in the _interests.html.erb partial.
What the heck would be the issue?
TIA
That just means that one of the interests doesn't have an associated post on the other end. Most likely it was deleted. This could have been prevented by the following:
class Post < ActiveRecord::Base
has_many :interests, :dependent => :destroy
end
In the meantime you should clean up the orphans in the database.
Edit: You claim this was already in your model, but if it was then it's not clear how you could have an orphaned Interest as the error indicates. Maybe it was created before you added the dependent clause? Again, go delete the orphans via SQL and then try again. If the problem resurfaces later you must be deleting without callbacks somewhere.
Regarding your size problem. You could be using current_user.interests.count. This is due to some magic with Rails associations. count is a special method on a Rails association that runs SQL. length is just an array method telling you how many items are in the array. Rails associations have a few special methods, but the rest of them they pass through to the array object transparently.
Further critiques: when you pass :object => #user_interests you are setting a variable with the name of the partial. So you could reference the local variable interests in the partial. However you are referencing #user_interests, so passing the object is not necessary. All else being equal, passing the object and using a local variable is probably better (it's more explicit, more of a functional-programming style), but in this case you are not making use of that.
Finally, stylewise, I may be wrong since I don't have the full context, but in general I would put the logged_in condition in the template rather than setting user_interests to an empty array if there is no logged in user. This would allow you to reference current_user.interests.count in the template and independently set the interests to be displayed (eg. for pagination) in #user_interests.

Resources