Testing for lack of associated objects in Ruby on Rails - 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.

Related

How to properly allow nil in rails

In my rails app my bands have many events. In the view of the bands it can show what events they have. My problem is if a new band is created it will throw an error in the view because it does not have an event.
I was reading about allow_nil and I wanted to use it in my bands model but Im not sure how to implement it. The documentation on it is pretty dry and not really helpful
class Band < ApplicationRecord
has_many :events :allow_nil true
end
Im not sure if the above way to do it is correct.
Rails 5.x
I think you are looking for optional: true, but it has to be added in the belongs_to side of the association, like this:
class Event < ApplicationRecord
belongs_to :band, optional: true
end
Rails 4.x
Rails 4 allows nil by default on any association, you just need to remove any validation that needs band_id presence.
I suspect what you're asking for (ActiveRecord-level data validations on associated models) isn't actually your problem. Your original question is about allow_nil but you state that (with emphasis mine):
In the view of the bands it can show what events they have. My problem is if a new band is created it will throw an error in the view because it does not have an event.
What you're probably doing is attempting to show a list of events by iterating over them using .each, but since the collection of events does not exist .each errors out. Instead, before you do:
<% events.each do |event| %> # or similar code that you're using
... # to iterate over the collection
<% end %>
You should check that events.present?:
<% if band.events.present? %>
<% band.events.each do |event| %>
...
<% end %>
<% else %>
# show something else
<% end %>
For the sake of completeness though, allow_nil is used on specific fields of a model to skip other validations on that field if that field is nil. ([See the documentation here.])(http://edgeguides.rubyonrails.org/active_record_validations.html#allow-nil)
It doesn't work in the code you posted in your question since there isn't an event_id field on your Band model—since it's a one-to-many association from Band to Events (assuming you setup your database correctly).

Finding if a user has already flagged an object

I've currently setup an instance where a Review has many Flags (via polymorphism) and a User has many flags. Everything works so far but I'd now like to make so that in the view if a user has flagged a particular review a message shows up, Flag Submitted instead of a link to flag it.
Right now I'm doing this by first gathering all of the flags that belong to the review then using any? to determine if any of them have the current user's id. I was wondering if there was a faster/more efficient way in doing this? It's the lookup chain that makes me comprehensive to leave it as is.
Note: flaggable is a local variable which in this case represents a #review:
<%= render "flag", flaggable: #review %>
class Review < ActiveRecord::Base
has_many :flags, as: :flaggable, dependent: :destroy
end
class Flag < ActiveRecord::Base
belongs_to :flaggable, polymorphic: true
belongs_to :owner, class_name: "User", foreign_key: :user_id
end
class User < ActiveRecord::Base
has_many :flags
end
<div>
<% if user_signed_in? && flaggable.flags.any? { |r| r.user_id == current_user.id } %>
<p>Flag Submitted</p>
<% else %>
<%= link_to "Flag", [:new, flaggable, :flag], remote: true %>
<% end %>
...
</div>
I think this will work:
<% if user_signed_in? && flaggable.flags.exists?(owner: current_user) %>
any? is an Array method that loops to check if any condition will return true for each element in the Array which is currently held in-memory.
Take note that flaggable.flags is an ActiveRecord::Relation object which extends the Array class, therefore you can use any Array methods for this. However, once you invoke any Array method, all flaggable.flags will be stored in-memory, which is by then, and only at that time will the loop to check matching conditions will take place.
exists? is an ActiveRecord method similar to where and find methods, in which this checks if any condition will return true for each row in the Table in the Database which is not in-memory, and is more optimised for searching, and therefore more efficient.
UPDATE: (To Explain Further)
Let's say you have this code
1 #users = User.where(is_admin: true)
2 #users = #users.where(age: 20)
3 <% #users.each do |user| %>
4 <%= user.name %>
5 <% end %>
6 <%= 'THERE IS A SANTA USER' if #users.any?{|user| user.name == 'Santa'} %>
At line 1, #users is an ActiveRecord::Relation, in which it does not yet access the DB. It only still currently is storing the DB Query in memory.
At line 2, #users has been added a condition that age should be 20, so the query now becomes something like #users = users-who-are-admin-and-age-is-20. This still is a Query stored in memory. Still no DB access happening yet.
At line 3, .each is called from #users. By calling this array method, #users which is an ActiveRecord::Relation now connects and queries the DB, which then returns an Array of users now available for looping as you would expect in a .each method. (You will notice that lines starting from 3 are code in the view file)
At line 6, .any? is called from #users. Since, #users which is still an ActiveRecord::Relation but has already accessed database and the Array is already in memory (because of line 3), then .any? will no longer access the DB, and will just behave like a normal .any? method of an Array class. You will then realize that if there are millions of User records in the DB, then #users will occupy a huge amount of memory which is not very good. It will be better to use .find_each which stores in-memory partial-by-partial with the downside of more DB calls.
On a side note, be careful of using .joins as opposed to .includes. .joins returns an Array object, while .includes returns an ActiveRecord::Relation. Both have their own use-cases.

How to eager loading when there are further conditions on association objects?

I am using Ruby on Rails 3.2.2 and I have the following has_many :through association in order to "order articles in categories":
class Article < ActiveRecord::Base
has_many :category_associations # Association objects
has_many :associated_categories, :through => :category_associations # Associated objects
end
class CategoryAssociation < ActiveRecord::Base
acts_as_list :scope => 'category_id = #{category_id} AND creator_user_id = #{creator_user_id}'
belongs_to :associated_article
belongs_to :creator_user, :foreign_key => 'creator_user_id'
end
On retrieving associated_categories I would like to load category_associations objects created by a user (note: the creator user is identified by the creator_user_id column present in the category_associations database table) because I need to display position values (note: the position attribute, an Integer, is required by the act_as_list gem and it is a column present in the category_associations database table) "near" each article title.
Practically speaking, in my view I would like to make something like the following in a proper and performant way (note: It is assumed that each article in #articles is "category-associated" by a user - the user refers to the mentioned creator user of category_associations):
<% #articles.each do |article| %>
<%= link_to(article.title, article_path(article)) %> (<%= # Display the article position in the given category %>)
<% end %>
Probably, I should "create" and "handle" a custom data structure (or, maybe, I should make some else...), but I do not how to proceed to accomplish what I am looking for.
At this time I am thinking that the eager loading is a good approach for my case because I could avoid the N + 1 queries problem since I have to state further conditions on association objects in order to:
retrieve specific attribute values (in my case those refer to position values) of association objects created by a given user;
"relate" (in some way, so that position values are suitable for displaing) each of those specific attribute values to the corresponding associated object.
I think, you are looking for this
#articles = Article.includes(:associated_categories)
This will eager load all your articles including both of its associations (associated_categories, associated_categories). Thus, it will avoid N+1 problem and wont fire queries when you iterate over #articles and its associations in your view.

Ruby : Access array object by index vs iterator

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

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