ActiveRecord find when include?() matches - ruby-on-rails-3

I have a Resource model with attribute subcategory_list, which is a comma-separated list of subcategories. Is it possible to do a find_by_x method (or equivalent) that pulls only those resources that belong to a certain subcategory?
So if given:
Resource.create(subcategory_list: "Fun, Games") # resource 1
Resource.create(subcategory_list: "Fun") # resource 2
Resource.create(subcategory_list: "Games") # resource 3
I would need a query to get both resources 1 and 2 when my input is "Fun". I can return ONLY "Fun" but not "Fun, Games" with the following
Resource.find_all_by_subcategory_list("Fun")
=> resource 2 (but not resource 1)
Is there a way to modify this query to include "Fun, Games" as well?

If subcategory_list is a comma-seperated string:
Resource.where('subcategory_list LIKE ?', "%Fun%")
If subcategory is an associated model:
Resource.joins(:subcategories).where('subcategories.name = ?', "Fun")

I agree with MrTheWalrus, but you might also want to check out acts_as_taggable_on by Michael Bleigh. It looks like what you are doing is tags associated with Resource and the acts_as_taggable_on gem will add a lot of power without having to write a lot of additional code, including what you are trying to do now as well.

Related

Find coincidences on related tables using SQL and Rails and know where the coincidences were

Well, I have the next Rails scope, when given a word I found the companies that match that word either on the name, description, city or in any of the products related with the company.
includes([:products, {city: :department}]).where("unaccent(LOWER(companies.name))
ILIKE('%#{term.parameterize.underscore.humanize.downcase}%')
OR unaccent(LOWER(companies.description))
ILIKE('%#{term.parameterize.underscore.humanize.downcase}%')
OR unaccent(LOWER(cities.name))
ILIKE('%#{term.parameterize.underscore.humanize.downcase}%')
OR unaccent(LOWER(products.name))
ILIKE('%#{term.parameterize.underscore.humanize.downcase}%')"
.gsub("ñ","n")).references(:products, :city, :department)
This works just fine, but know I need to know in which (name, description, city or products) was the coincidence found.
I have thought in the next solutions but I am not sure if is efficient or good enough.
Solution.
Separate the scope in 4 different queries, then use a loop and an aux column to fill with something like "Coincidence founds in, name and description" on each different query.
then use something like this
query1 | query2 | query3 | query4 # to merge the arrays
For the record my Rails app is using Postgres 9.4
I think you have a good start by separating your results into four queries.
When you merge them, you want to maintain a way to see which query it came from.
If you're fine with your results being hashes, you can do this:
results = []
results.concat query1.map { |record| record.attributes.merge(query: "query1") }
results.concat query2.map { |record| record.attributes.merge(query: "query2") }
# etc
If you want your results to be active record objects, you can add a virtual attribute and do something similar
# in the model, add a virtual attribute (not stored in db)
attr_accessor :query
# in the controller
records = []
records.concat query1.map { |record| record.query = "query1"; record}
records.concat query2.map { |record| record.query = "query2"; record}
# etc.

Rails Order by frequency of a column in another table

I have a table KmRelationship which associates Keywords and Movies
In keyword index I would like to list all keywords that appear most frequently in the KmRelationships table and only take(20)
.order doesn't seem to work no matter how I use it and where I put it and same for sort_by
It sounds relatively straight forward but i just can't seem to get it to work
Any ideas?
Assuming your KmRelationship table has keyword_id:
top_keywords = KmRelationship.select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20)
This may not look right in your console output, but that's because rails doesn't build out an object attribute for the calculated frequency column.
You can see the results like this:
top_keywords.each {|k| puts "#{k.keyword_id} : #{k.freqency}" }
To put this to good use, you can then map out your actual Keyword objects:
class Keyword < ActiveRecord::Base
# other stuff
def self.most_popular
KmRelationship.
select('keyword_id, count(keyword_id) as frequency').
order('frequency desc').
group('keyword_id').
take(20).
map(&:keyword)
end
end
And call with:
Keyword.most_popular
#posts = Post.select([:id, :title]).order("created_at desc").limit(6)
I have this listed in my controller index method which allows the the order to show the last post with a limit of 6. It might be something similar to what you are trying to do. This code actually reflects a most recent post on my home page.

Rails 3 - Select objects which do not include associated model?

(Using Rails 3)
I have 2 models (Vehicle and Capabilities) in a has_many through association.
So Vehicle 1 can have Capability 1 (eg towing), Capability 2 (eg passenger), Capability 3 (eg flying), etc.
v = Vehicle.first
v.capabilities.pluck(:name) #=> will give something like ['towing', 'passenger', 'flying']
I want to find all vehicles which must not have a particular capability, eg all vehicles which cannot fly.
I have tried queries similar to this below but it still includes flying vehicles, I think mainly because the airplane also has other capabilities.
non_flying = Vehicle.includes(:capabilities).where('capabilities.id NOT IN (?)', [2,3])
non_flying.first.capabilities.pluck(:name) #=> will give something like ['towing'].
Note that the flying capability is not included, but I just do not want this vehicle returned at all. How would I write this?
If possible, I would rather not use meta_wheel or squeel gems, but any arel_table implementation is welcome unless there is a simpler solution.
Try this query
non_flying = Vehicle.all - Vehicle.includes(:capabilities).where('capabilities.id IN (?)', [2,3]).all
I ended up doing something similar to this, inspired by Thaha kp's answer.
# Get all flying vehicles first
subquery = Vehicle.joins(:capabilities).where("capabilities.id IN (?)", 3).pluck("vehicles.id")
# Then get all vehicles not in this flying vehicles array
non_flying = Vehicle.where('vehicles.id NOT IN (?)', subquery).all

Sorting a Rails database table by a column in an associated model with additional scoping

I am new to Rails. I am trying to implement something like Ryan Bates' sortable table columns code (Railscast #228) on a legacy database. My question is very similar to "Sorting a Rails database table by a column in an associated model", but I can't seem to solve mine based on the answers there.
I want to be able to sort my list of projects in the index view by the udtid in the entityudfstorage table (class Ent), i.e., by project.ent.udtid. I have an additional consideration, in that each project matches a number of ent rows, so I need to scope to match to where ent.rowindex != 0.
The Models:
class Project < ActiveRecord::Base
has_many :ent, :foreign_key => "attachtoid"
has_many :samples, :foreign_key => "projectid"
class Ent < ActiveRecord::Base
set_table_name("entityudfstorage")
belongs_to :project, :foreign_key => "attachtoid"
scope :rowindex, where('entityudfstorage.rowindex != ? ', "0")
project index view:
<tr>
<th><%= sortable "name", "Name" %></th>
<th><%= sortable "projecttype", "Project Type" %> </th>
</tr>
<tr>
<td><%= project.name %></td>
<td><%= project.ent.rowindex.first.udtid %></td>
</tr>
project controller
def list
#projects = Project.order(sort_column + " " + sort_direction)
end
I've been trying to figure out what I can put in the "sort_column" for projecttype which would get it to sort by the associated field project.ent.rowindex.first.udtid (the same way that "name" works in the controller to sort by project.name).
I tried putting in a scope in projects of
scope :by_udtids, Project.joins("left join ent on projects.projectid = ent.attachtoid").where('ent.rowindex != ?', 0).order("ent.udtid DESC")
and then tried this in the project controller.
if sort_column == "projecttype"
#projects = Project.by_udtids
else
#projects = Project.order(sort_column + " " + sort_direction)
The result is that the project index page shows up with the proper data in the columns, but when I click on the "Project Type" link header, it does not sort (whereas, if I click on the "Name" link header, it does sort. The logs I can see in the server terminal are the same for both clicks, and the query's seem correct..
Started GET "/projects?direction=asc&sort=projecttype" for 128.208.10.200 at 2013-08-29 07:47:52 -0700
Processing by ProjectsController#index as HTML
Parameters: {"direction"=>"asc", "sort"=>"projecttype"}
Project Load (1.5ms) SELECT "project".* FROM "project" ORDER BY name asc
Ent Load (0.4ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 602 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
CACHE (0.0ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 602 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
(0.3ms) SELECT COUNT(*) FROM "sample" WHERE "sample"."projectid" = 602
Ent Load (0.3ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 603 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
CACHE (0.0ms) SELECT "entityudfstorage".* FROM "entityudfstorage" WHERE "entityudfstorage"."attachtoid" = 603 AND (entityudfstorage.rowindex != '0' ) LIMIT 1
(0.2ms) SELECT COUNT(*) FROM "sample" WHERE "sample"."projectid" = 603
Rendered projects/list.html.erb within layouts/admin (478.7ms)
Completed 200 OK in 487ms (Views: 398.7ms | ActiveRecord: 87.9ms)
[2013-08-29 07:55:27] WARN Could not determine content-length of response body. Set content- length of the response or set Response#chunked = true
Started GET "/assets/jquery.js?body=1" for 128.208.10.200 at 2013-08-29 07:55:28 -0700
Served asset /jquery.js - 304 Not Modified (0ms)
[2013-08-29 07:55:28] WARN Could not determine content-length of response body. Set content- length of the response or set Response#chunked = true
Much appreciate any insight!
You don't actually say what "doesn't work" means in this context. I would guess that it either means you get an error message or that the Projects aren't sorted the way you expect.
If it's an error message, you should include it with your question, but the first thing I'd do in your place is probably to start using the Rails conventions for naming and referring to things. If your associated class is UserDefinedField, Rails is going to expect the table to be named user_defined_fields and the association to be specified as has_many :user_defined_fields. It also expects fields to be in snake case (attach_to_id, not attachtoid), but aside from having to specify every foreign key everywhere, that probably won't cause errors. Looking at your code, I would expect Rails to be complaining about the association name any time it loaded the Project model. You should either change these things to match the conventions, or (if you have a good reason to use these names), tell Rails what's up by specifying the class and table names in your associations.
If it's an incorrect response order, I'm less certain what the issue is. One thing that jumps out immediately is that you're going to get the same Project returned multiple times with this scope, once for each associated UserDefinedField. If you don't want that to happen, you'll need to add a group clause to your scope and some sort of aggregation for the userdefinedfields.udtids. This might look something like:
scope :by_udtids, Project.
joins("left join userdefinedfields on projects.projectid = userdefinedfields.attachtoid").
where('userdefinedfields.rowindex != ?', 0).
group('projects.id').
order("max(userdefinedfields.udtid) DESC")
Edit:
It looks like your current problem is that the scope isn't getting used at all. Here's a couple of reasons that might be the case:
I notice that your controller action is called ProjectsController#list, but that your log says the request with the parameters is being processed by ProjectsController#index. The call to the list action doesn't appear to be running any queries, which sounds as though perhaps it isn't doing anything that triggers actually loading the objects. Could this simply be a routing or template error?
Given that you say the query being run is the same in both cases, if the correct action is being called, it seems likely that your conditional (sort_column == "projecttype") is returning false, even when you pass the parameters. Even if the scope wasn't quite correct, you would otherwise still see at least a different query there. Is it possible that you're simply forgetting to set sort_column = params[:sort]? Try temporarily removing the conditional - just always use #projects = Project.by_udtid. See if you get a different query then.
Side note on the scope/query itself. If I understand your comment correctly, you want to sort by the udtid of the Ent with the lowest nonzero rowindex. This is going to be tricky, and the details will depend on your database (grouping, especially complex grouping, is one of the things that works fairly differently in mySQL vs. PostGreSQL, the two most common databases for Rails apps). The concept is called a 'groupwise maximum', and your best bet is probably to search for that phrase in conjunction with your database name.

Rails, Ransack: How to search HABTM relationship for "all" matches instead of "any"

I'm wondering if anyone has experience using Ransack with HABTM relationships. My app has photos which have a habtm relationship with terms (terms are like tags). Here's a simplified explanation of what I'm experiencing:
I have two photos: Photo 1 and Photo 2. They have the following terms:
Photo 1: A, B, C
Photo 2: A, B, D
I built a ransack form, and I make checkboxes in the search form for all the terms, like so:
- terms.each do |t|
= check_box_tag 'q[terms_id_in][]', t.id
If I use: q[terms_id_in][] and I check "A, C" my results are Photo 1 and Photo 2. I only want Photo 1, because I asked for A and C, in this query I don't care about B or D but I want both A and C to be present on a given result.
If I use q[terms_id_in_all][] my results are nil, because neither photo includes only A and C. Or, perhaps, because there's only one term per join, so no join matches both A and C. Regardless, I want just Photo 1 to be returned.
If I use any variety of q[terms_id_eq][] I never get any results, so I don't think that works in this case.
So, given a habtm join, how do you search for models that match the given values while ignoring not given values?
Or, for any rails/sql gurus not familiar with Ransack, how else might you go about creating a search form like I'm describing for a model with a habtm join?
Update: per the answer to related question, I've now gotten as far as constructing an Arel query that correctly matches this. Somehow you're supposed to be able to use Arel nodes as ransackers, or as cdesrosiers pointed out, as custom predicates, but thus far I haven't gotten that working.
Per that answer, I setup the following ransack initializer:
Ransack.configure do |config|
config.add_predicate 'has_terms',
:arel_predicate => 'in',
:formatter => proc {|term_ids| Photo.terms_subquery(term_ids)},
:validator => proc {|v| v.present?},
:compounds => true
end
... and then setup the following method on Photo:
def self.terms_subquery(term_ids)
photos = Arel::Table.new(:photos)
terms = Arel::Table.new(:terms)
photos_terms = Arel::Table.new(:photos_terms)
photos[:id].in(
photos.project(photos[:id])
.join(photos_terms).on(photos[:id].eq(photos_terms[:photo_id]))
.join(terms).on(photos_terms[:term_id].eq(terms[:id]))
.where(terms[:id].in(term_ids))
.group(photos.columns)
.having(terms[:id].count.eq(term_ids.length))
).to_sql
end
Unfortunately this doesn't seem to work. While terms_subquery produces the correct SQL, the result of Photo.search(:has_terms => [2,5]).result.to_sql is just "SELECT \"photos\".* FROM \"photos\" "
With a custom ransack predicate defined as in my answer to your related question, this should work with a simple change to your markup:
- terms.each do |t|
= check_box_tag 'q[id_has_terms][]', t.id
UPDATE
The :formatter doesn't do what I thought, and seeing as how the Ransack repo makes not a single mention of "subquery," you may not be able to use it for what you're trying to do, after all. All available options seem to be exhausted, so there would be nothing left to do but monkey patch.
Why not just skip ransack and query the "photos" table as you normally would with active record (or even with the Arel query you now have)? You already know the query works. Is there a specific benefit you hoped to reap from using Ransack?