Finding if a user has already flagged an object - ruby-on-rails

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.

Related

sunspot solr how to search multiple models correctly? All examples online fail

How would one correctly search multiple models in SunSpot Solr?
Profile model
has_one :match
searchable do
string :country
string :state
string :city
end
Match model
belongs_to :profile
searchable do
string :looking_for_education
integer :age_from
integer :age_to
end
ProfilesController#Index
def index
#search = Sunspot.search Profile, Match do
with(:country, params[:country])
with(:state, params[:state])
with(:looking_for_education, params[:looking_for_education]) <= from the 2nd model
end
#profiles = #search.results
end
This fails with:
Using a with statement like
with(:age).between(params[:age_from]..params[:age_to])
undefined method `gsub' for nil:NilClass
Removing the
with(:age).between(params[:age_from]..params[:age_to]) line then it tries to
then it tries to load the
view app/views/educators/educator.html.haml
which does not exist ( im only using
/app/views/profiles/_profile.html.haml
to show profiles
EDIT #1:
What are good opensource projects in ruby on rails that use sunspot and solr in a bit more advanced way to have a look at? Maybe I can find the answer there. Any answer in this direction will also be accepted the bounty if it yields in resulting this issue, thx!
The method you've found for searching multiple models is correct. However, it appears that the meaning of your search is not what you intended. It looks as if you're trying to say:
Give me all Profile records with these country and state values, and whose Match record has this looking_for_education value
Your search, however, says:
Give me all records of type Profile or Match that have all of these country, state and looking_for_education values
Because neither Profile nor Match have all of these fields in their respective searchable blocks, no single record can match the conditions you specify.
If I'm correct about your intended behaviour above, then you need to include the profile's associated match information in the profile's searchable block, like so:
class Profile < ActiveRecord::Base
has_one :match
searchable do
string(:country)
string(:state)
string(:city)
string(:looking_for_education) { match.looking_for_education }
integer(:age_from) { match.age_from }
integer(:age_to) { match.age_to }
end
end
Here, we've told Sunspot to index properties of the profile's match association as if they lived on the profile itself. In the respective blocks, we've told Sunspot how to populate these values when the profile is indexed.
This will allow you to write your search using only the Profile model:
def index
#search = Sunspot.search Profile do
with(:country, params[:country])
with(:state, params[:state])
with(:looking_for_education, params[:looking_for_education])
with(:age).between(params[:age_from]..params[:age_to])
end
#profiles = #search.results
end
This search will return only Profile records, while still reflecting the properties of each profile's match association, because we stored them when the profile was indexed.
Note that this increases complexity when you index your models. If a Match record changes, its associated profile now needs to be reindexed to reflect those changes.
This is what i am doing when i have to search for multiple models
Sunspot.search [Model1, Model2] do
....
end
#moises-zaragoza answered correctly your question but you have more issues than you think with what you want to do.
The first error:
Using a with statement like
with(:age).between(params[:age_from]..params[:age_to])
undefined method `gsub' for nil:NilClass
Is most likely produced because params[:age_from] and/or params[:age_to] are nil. I can't assure it because you haven't shown the stacktrace. You can fix by filter only when they are present:
with(:age).between(params[:age_from]..params[:age_to]) if params[:age_from].present? and params[:age_to].present?
The second error
Related to your views. I am assuming you are rendering a collection or object with one of the rails helper partial object helpers, without specifying the partial (again, without the code this is more of a good guess than anything else):
<%= render #results %>
or
<% #results.each do |result| %>
<%= render result %>
<% end %>
When Rails does not have a partial specified, it guesses the partial name depending on the object type. In this case, if your object is for example of class Educator, which might be a subclass of Profile, Rails will look for the partial 'educators/_educator.html.erb. Make sure you render the proper partial depending on the object type and this will ensure you render what you want.
Here the answer for search different model on matching string using with
searchable(:auto_index => AppConfig.solr.auto_index) do
string :category_name, :stored => true
text :content, :stored => true
text :title
string :company_id, :stored => true
time :published_on
end
search do |q|
if params[:keyword].present?
q.fulltext params[:keyword] do
fields(:deal_data)
end
end
if (ids = params["company_id"]).present?
ids = ids.split(",")
q.with(:company_id,ids) #here company id formate should be ["***","***"]
end
end

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.

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

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.

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