ruby solr search within object rather than in models - ruby-on-rails

I'm using sunspot_solr gem with my rails app and below is my method for searching users
users = customers_object.search do
fulltext params[:query]
end.results
but the search does not happen in my customers_object objects but it searches within the whole model, I could not find any resources on how to achieve this, is this possible ? if yes can anyone guide me how to do it ?
Thanks in advance

I'm not aware of such syntax
current_user.customers.search
Guess it just works, because the search method is defined on the Customer class.
You should index all user_ids within Customer Index, then pass the proper with scope.
class Customer
has_and_belongs_to_many :users
searchable do
integer :user_ids, multiple: true
....
end
end
Customer.search do
with :user_ids, current_user.id
fulltext param[:query]
end

finally found it, I was also using cancan gem and I was supossed to do User.accessible_by(current_ability) rather than current_users_customers_object, may help someone

Related

Devise and acts_as_votable: where votes_for voter_id => current_user.id

I'm using the devise and acts_as_votable gems to allow users to cast their votes on posts. My current objective is to query a list of the posts liked by the user.
Looking around in the Rails console I found out that when a post is voted on a votes_for table gets appended on containing information like the timestamp, vote weight and the voter_id which is what I would like to call upon.
Here's what the user controller looks like (user controller because I'm attempting to query the likes on the users/show page):
#user = current_user
#links = Link.all
#likes = #links.where.votes_for(:voter_id => #user.id) // line I'm trying to figure out, I reckon it'd look something like this
I could definitely be going about this the wrong way altogether, if that's the case I'd just appreciate some direction.
Explanation would be appreciated... I'm learning the fundamentals of Rails and finding its naming conventions convoluted and hard to follow.
If you're using Devise, I guess the current_user method should be available in your view and you shouldn't need a reassignment to #user
To understand how ActiveRecord works you might want to look into the documentation here.
As for the links and the voter_id, here's the way I think your query should be:
#likes = Link.where(votes_fors: {voter_id: current_user.id}) # OR Link.where(voter: current_user)
Basically the query will be transformed to a SQL query which says:
"SELECT * FROM links INNER JOIN votes_fors ON links.id = votes_fors.votable_type='Link' WHERE votes_fors.voter_id = ?" [voter_id, 1]
You get the idea?
A better approach would be to use the ActiveRecord methods to fetch these collections e.g
class User
has_many :votes_fors, foreign_key: :voter_id
has_many :favorites, through: :votes_fors, source: :votable # not really sure of this syntax but it should work I think.
end
With the above, you should be able to do:
current_user.favorites which would fetch you all the links voted by the user.

rails cancan sunspot search not working

I am using 'sunspot_solr', '~> 2.0.0' and 'cancan', '~> 1.6.8' gems in my rails app but I can't do a successful search using those two, say I have a resource called Photos and this is my search query
photos = Photos.accessible_by(current_ability).search do
fulltext params[:query]
end.results
but the search happens on all photos not on those that belongs to current user, I believe current_user.photos and Photos.accessible_by(current_ability) are the same.
My ability.rb has this permissions
can :list, Photos
can [:read, :create, :update, :destroy], Photos, user_id: user.id
Any help would be much appreciated.
I don't think that the Sunspot search will filter based on a given scope, it just takes a model argument so it will search across all instances.
You could do the search first and then filter the results but that would mess up paging if you are using Sunspot to do that.
A better solution might be to index the user_id attribute in Solr so that you can do a search filtered by that as well as by the free text input. It isn't ideal because you would be duplicating authorisation logic.
So in your model you would need:
searchable do
...
integer :user_id
end
You would need to rebuild the search index.
And then include it in your search filter with something like:
photos = Photos.search do
fulltext params[:query]
with(:user_id).equal_to(current_ability.user.id)
end.results
There is a discussion of a similar problem here.
Even if I consider Steve answer correct you will have two different places in which you define permissions for the photos, and this is not nice because we are actually using cancan for that.
I would prefer using something like:
photos = Photo.search do
fulltext params[:query]
with(:id, Photo.accessible_by(current_ability).pluck(:id))
end.results
so you do not have to duplicate the logic for permissions.
btw: why Photos instead of Photo?

How do I add Sort by options?

I have a 'Sort By' dropdown on an events page where users can view a number of events and I'd like to allow users to Sort the events by Name (Alphebetical), Date (Created_At), and perhaps (Number of people attending hi/low).
How can I achieve this? I'm guessing with a default scope: order, Event.where(:name, ASC) for example but i'm not sure as I've never done this before.. Also how would I display/use the scope in the dropdown?
I recommed you the very-usefull gem has_scope:
https://github.com/plataformatec/has_scope
What you could do with it:
class Event < ActiveRecord::Base
scope ordered, lambda do |attribute, order|
return where("FALSE") unless self.attribute_names.include?(attribute.to_s)
order(attribute.to_sym, order.to_sym
end
class EventsController < ApplicationController
has_scope :ordered do |controller, scope, value|
scope.ordered(value, :desc)
end
# view
options = [['Name', 'name'], ['Date', 'date']]
select_tag("ordered", options_for_select(options, selected: params[:ordered]), onchange: "window.location.href = window.location.origin + window.location.pathname + '?ordered=' + $(this).val();"
I did not test the view, you might have to change the code a little bit. The javascript redirect does not support extra GET params!
Have a look at the RailsCasts
http://railscasts.com/episodes/240-search-sort-paginate-with-ajax
This also includes pagination and simple searches. However, you can take from it the part that you're looking for.
For active record you just need to append .order(symbol) to your active record query.
for alphabetic you could do.
Object.where(ARRGUMENT).order(:name)
or for all
Object.order(:name)
You don't need to use a scope for this it is over kill for something this simple.
If you want to create an interactive table then you might want to consider a Javascript solution. This is the one that has worked the best for me.
https://datatables.net/
I have used and currently use this gem with great success.
https://github.com/rweng/jquery-datatables-rails

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

Ruby on Rails Active Admin - Duplicate Records showing for HABTM

I am designing a basic file manager (the Asset model) in the Active Admin gem. Each Asset HABTM Groups, and vice-versa.
In my active_admin Asset resource I have a filter where I want to be able to
select multiple groups to filter by, so I added:
filter :groups_id, :as => :check_boxes, :collection => proc {Group.all}
All of the groups show up as checkboxes as expected. However, if I have asset_1, asset_2 and I have group_1 assigned to asset_1 and asset_2, and group_2 to asset_2, when I
filter by both roles, asset_2 lists itself twice.
How can I restrict the filter to use only "distinct" or "unique" assets to be returned?
I also have another problem, which is that the filters are not working at all in any of my scopes.
A quick update on Will's answer. I'm running Rails 5.0 and ActiveAdmin 1.0, and clean_search_params returned an error. But this worked instead:
def apply_filtering(chain)
super
#search.result(distinct: true)
end
Thanks!
Active admin read indicates to add
distinct: true
to get unique results.
To apply that to active admin, I'm using doing that like this:
controller do
def apply_filtering(chain)
#search = chain.ransack clean_search_params params[:q]
#search.result(distinct: true)
end
end
has_and_belongs_to_many accepts a :uniq option which ensures that only uniq records will be returned. Setting this in your model should do the trick.
class MyModel
has_and_belongs_to_many :things, :uniq => true
end
... and, a quick addition Alex's answer:
If you want to do this for all controllers in your app, you can add this to an initializer (mine's called active_admin_patches.rb) -
# This guarantees that the results for a filtered #index page search do not appear more than once, on any #index page in the AA app
# TODO: THIS WILL PROBABLY FAIL WITH SOME FUTURE UPDATE, SO BE READY TO UPDATE IT FROM THE LATEST GEM SOURCE
module ActiveAdmin::ResourceController::DataAccess
# Applies any Ransack search methods to the currently scoped collection.
# Both `search` and `ransack` are provided, but we use `ransack` to prevent conflicts.
def apply_filtering(chain)
#search = chain.ransack(params[:q] || {})
# This is the original line
# #search.result
# This is the patch
#search.result(distinct: true)
end
end
I'm not sure why anybody wouldn't want this to be the default behavior, but there's probably a reason. Hmm, maybe for cases where a column of the index view is one of the non-distinct rows. Yeah, that must be it.
Also, there's bound to be a better way to patch this less intrusively, but I'm in a hurry. :-)

Resources