basic modification of default model output with scope - sql

I find myself doing the same things over and over again just to make one small modification to standard model output. I have a series of tables that I store information about products, etc. and all of which store prices. The prices are stored in US dollars but the output depends on the currency the user wants which is stored in a their session.
Examples:
Product Detail Blah Price
Hammer Red More 5.00
Nail Blue Stuff 3.99
Is there a simple robust way to modify the output so that when i call:
Product.all
I could attach something like
Product.all.currency('EUR')
Product.find(22).currency('EUR')
Product.find(:all, :conditions => 'etc etc').currency('EUR')
or
Product.all.currency(0.69)
and simply multiply all of the items in the Price column? Could named_scope do this?

try reordering your chaining like:
Product.currency(0.69).all
I have not tested that, but you may have issue with other arbitrary conditions since your :select contains *

Nevermind...
named_scope :currency, :select => '*, price * 0.63 AS price'
seemed to work except I can't chain it to normal finds as I would like.

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 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

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?

Rails 3 selecting only values

In rails 3, I would like to do the following:
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id")
This works, but i get the following from the DB:
[{"some_other_connection_id":254},{"some_other_connection_id":315}]
Now, those id-s are the ones I need, but I am uncapable of making a query that only gives me the ids. I do not want to have to itterate over the resulst, only to get those numbers out. Are there any way for me to do this with something like :
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id").values()
Or something of that nautre?
I have been trying with the ".select_values()" found at Git-hub, but it only returns "some_other_connection_id".
I am not an expert in rails, so this info might be helpful also:
The "SomeModel" is a connecting table, for a many-to-many relation in one of my other models. So, accually what I am trying to do is to, from the array of IDs, get all the entries from the other side of the connection. Basicly I have the source ids, and i want to get the data from the models with all the target ids. If there is a magic way of getting these without me having to do all the sql myself (with some help from active record) it would be really nice!
Thanks :)
Try pluck method
SomeModel.where(:some => condition).pluck("some_field")
it works like
SomeModel.where(:some => condition).select("some_field").map(&:some_field)
SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id").map &:some_other_connection_id
This is essentially a shorthand for:
results = SomeModel.where(:some_connection_id => anArrayOfIds).select("some_other_connection_id")
results.map {|row| row.some_other_connection_id}
Look at Array#map for details on map method.
Beware that there is no lazy loading here, as it iterates over the results, but it shouldn't be a problem, unless you want to add more constructs to you query or retrieve some associated objects(which should not be the case as you haven't got the ids for loading the associated objects).

Encapsulating SQL in a named_scope

I was wondering if there was a way to use "find_by_sql" within a named_scope. I'd like to treat custom sql as named_scope so I can chain it to my existing named_scopes. It would also be good for optimizing a sql snippet I use frequently.
While you can put any SQL you like in the conditions of a named scope, if you then call find_by_sql then the 'scopes' get thrown away.
Given:
class Item
# Anything you can put in an sql WHERE you can put here
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
end
This works (it just sticks the SQL string in there - if you have more than one they get joined with AND)
Item.mine.find :all
=> SELECT * FROM items WHERE ('user_id' = 887 and IS_A_NINJA() = 1)
However, this doesn't
Items.mine.find_by_sql 'select * from items limit 1'
=> select * from items limit 1
So the answer is "No". If you think about what has to happen behind the scenes then this makes a lot of sense. In order to build the SQL rails has to know how it fits together.
When you create normal queries, the select, joins, conditions, etc are all broken up into distinct pieces. Rails knows that it can add things to the conditions without affecting everything else (which is how with_scope and named_scope work).
With find_by_sql however, you just give rails a big string. It doesn't know what goes where, so it's not safe for it to go in and add the things it would need to add for the scopes to work.
This doesn't address exactly what you asked about, but you might investigate 'contruct_finder_sql'. It lets you can get the SQL of a named scope.
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
named_scope :additional {
:condtions => mine.send(:construct_finder_sql,{}) + " additional = 'foo'"
}
sure why not
:named_scope :conditions => [ your sql ]