Creating a scope on an ActiveRecord Model - ruby

I have a ActiveRecord model called Panda with a column called variant, which can consist of values like ‘bam-abc’, ‘bam-123’, ‘boo-abc’ and ‘boo-123’ I want to create a scope which selects all records where the variant starts with ‘boo’.
In the console, I can select those records (starting with ‘boo') with the following:
Panda.select{|p| p.variant.starts_with? 'boo'}
Is there a way to turn that into a scope on the Panda class? I need to be able to do a 'to_sql' on the scope for my RSpec tests.

You'd want to use a scope that sends a LIKE into the database, something like:
scope :boos, -> { where('pandas.variants like ?', 'boo%') }
or equivalently, use a class method:
def self.boos
where('pandas.variants like ?', 'boo%')
end
Then you can say things like:
Panda.boos.where(...)
Panda.where(...).boos
Panda.boos.where(...).to_sql
Panda.where(...).boos.to_sql
You only need to use the pandas prefix on the column name if you think you'll be doing JOINs with other tables that might leave the variant name ambiguous. If you'll never be doing JOINs or you'll only ever have one variant column in your database then you could use one of:
scope :boos, -> { where('variants like ?', 'boo%') }
def self.boos
where('variants like ?', 'boo%')
end

Add the line below to the Panda class
scope :boo, -> { where('variant LIKE ?', 'boo%') }
You can then use Panda.boo to get all the records with variant starting with boo. Panda.boo.to_sql will give you the sql

Related

Rails 4 - Comparing two variables in scope

How do you compare two variables in a custom scope with Rails 4 ?
Documentation and common examples always show basic comparisons with table attributes
Like in this example :
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
But in my case, I want to compare a datetime from my Model to a date string coming from a form submission
scope :departure, -> (departure) { where("departure = ?", "%#{departure}%")}
The problem is that I want to do some manipulation to my model variable before the comparison (ex, convert to string or change format)
I tried with different methods like this one
def self.departure(departure)
#date_without_time = flights.departure.to_date.to_s
where(#date_without_time, "= ?", departure)
end
But the condition end up like this (2012-01-03) instead of (departure = "%#{departure}%")
Is there a better way to do it?
To be more general, how do you create methods or scope to compare two variables instead of only comparing one variable to a model attribute?
In this case, I want to compare my model attribute which is a Datetime with a form submitted string date, so before I can compare the two dates I need to do some treatment on my model attribute since I need to ignore the time part and format it to a string
Update :
I ended up making it work using this method, but I'd still like to know if it's possible to call class or instance method on a Model column in a scope or if there is a better way to handle the Datetime/form date comparison in a better way
scope :departure_s, -> (departure) { where("departure > ?", departure.to_datetime.beginning_of_day)}
scope :departure_e, -> (departure) { where("departure < ?", departure.to_datetime.end_of_day)}
scope :departure, -> (departure) { departure_e(departure).departure_s(departure)}
You're taking the wrong approach trying to manipulate models. You're running a query to return models, you don't have any models to manipulate yet.
You can accomplish what you're trying to do on the database level with SQL. I would write the scope like this
if the departure variable is a string that represents a date in the format 'yyyy-mm-dd' you can do
scope :departure, -> (departure) { where("date(departure) = ?", departure )}
This assumes you're using postgres, it might be slightly different for mysql. the date() function converts a datetime to date on the database elevel

Why can I not query my ActiveRecord relation?

I have a relation on my ActiveRecord object in Rails ( 3) and having got a little rusty with AR I'm not entirely sure why I can't query it.
I have a deadline which wraps a date in a few other bits and bobs.
I have a Task which belongs_to a deadline. The Task also has a :by_deadline scope, which works if I query it directly with Task.by_deadline( deadline ). It looks a bit like this:
scope :by_deadline, lambda{ |deadline| where("deadline_id = ?", deadline.id ) }
I have a Channel which has_many tasks.
I want to be able to find all Tasks on my Channel that belong to a particular deadline. I am currently working in a spec testing environment.
This works:
result = []
#channel.tasks.each do |task|
result << task if task.deadline_id = #deadline.id
end
I get exactly what I would expect.
However, I am confused by why I can't use something like:
#channel.tasks.by_deadline #deadline
Or if not that:
#channel.tasks.find( :deadline => #deadline )
Or at least:
#channel.tasks.find( :deadline_id=> #deadline.id )
Or even:
#channel.tasks.find_by_deadline( #deadline )
Those seem like they should do something, but all return nothing in my test, when called right next to the above block, which works fine.
I am guessing that this is something to do with ActiveRecord going to the database rather than using an in-memory collection, but what do I need to ensure that it works or am I totally misapprehending how relationships are supposed to behave?

Writing scope, join and order for a Model

My model is like this:
Program has_many Measures
and then
Measures has_many Targets
and Target table has a column named value
My query is like this:
#programs2 = Program.includes([measures: :targets])
.some_scope
.where('organization_id = 1')
.limit(2)
I don't know where or how to write the some_scope part of the query.
The query starts with Program.includes so I think it should be defined in the Program model but the problem I have is that measures: :targets . How do I define a join for them. If it was just one table I know I can do like this:
scope :salary, :joins => :registry, :order => "money DESC"
I need something similar for this one too, but this has has two tables like I explained above. I don't know how to write that one.
Try,
scope :some_scope, joins(mesures: :targets)

Rails 3 Query: How to get most viewed products/articles/whatever?

I always wondered how to query and get results that doesn't fit in a model. Similar how it's done using LINQ and projecting into anonymous objects.
So here's the simple schema:
# Product.rb
class Product < ActiveRecord::Base
has_many :product_views
# attributes: id, name, description, created_at, updated_at
end
# ProductView.rb
class ProductView < ActiveRecord::Base
belongs_to :product
# attributes: id, product_id, request_ip, created_at, updated_at
end
Basically I need to get a list of Products (preferably just id and name) along with the count of views it had. Obviously ordered by view count desc.
This is the SQL I want to get:
select
p.id,
p.name,
count(pv.product_id) as views
from
product_views pv
inner join
products p on pv.product_id = p.id
group by
pv.product_id
order by
count(product_id) desc
I tried the following and similar, but I'm getting ProductView objects, and I would like to get just an array or whatever.
ProductView.includes(:product)
.group('product_id')
.select("products.id, products.name, count(product_id)")
This kind of thing are trivial using plain SQL or LINQ, but I find myself stucked with this kind of queries in Rails. Maybe I'm not thinking in the famous 'rails way', maybe I'm missing something obvious.
So how do you do this kind of queries in Rails 3, and specifically this one? Any suggestions to improve the way I'm doing this are welcome.
Thank you
You can use Arel to do what you're looking for:
products = Product.arel_table
product_views = ProductView.arel_table
# expanded for readability:
sql = products.join(product_views)
.on(product_views[:product_id].eq(product[:id]))
.group(product_views[:product_id])
.order('views DESC')
.project(products[:id],
products[:name],
product_views[:id].count.as('views'))
products_with_views = Product.connection.select_all(sql.to_sql) # or select_rows to just get the values
Yes, it is long, but Arel is a very smart way to deal with creating complex queries that can be reused regardless of the database type.
Within a class method in the Product class:
Product.includes(:product_views).all.map { |p| [p.id, p.name, p.product_views.size] }
Then sort it however you want.
I don't know if there's a way to do it using your models. I would probably resort to:
Product.connection.select_rows(sql)
Which will give you an array of arrays. You can use select_all if you'd rather have an array of hashes.
Try this:
#product = Product.find(#product_id)
#product_views = #product.product_views.count
(Source - http://ar.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html#M000292)
Hope this helps!

How to use Rails 3 scope to filter on habtm join table where the associated records don't exist?

I have an Author model which habtm :feeds. Using Rails 3 want to setup a scope that finds all authors that have no associated feeds.
class Author < ActiveRecord::Base
has_and_belongs_to_many :feeds
scope :without_feed, joins(:feeds).where("authors_feeds.feed_id is null")
end
...doesn't seem to work. It feels like a simple thing. What am I missing here?
To my knowledge ActiveRecord/Arel do not have a means of defining outer joins. So you'll have to write a bit more SQL than normal. Something like this should do the trick:
scope :without_feed, joins('left outer join authors_feeds on authors.id=authors_feeds.author_id').where('authors_feeds.feed_id is null')
I am of course guessing at your table names and foreign keys. But that should give you the picture.
In Rails >= 5, you can do it like this:
scope :without_feed, -> {
left_outer_joins(:feeds)
.where(authors_feeds: { author_id: nil })
}
scope :with_feed, -> {
left_outer_joins(:feeds)
.where.not(authors_feeds: { author_id: nil })
}

Resources