Why can I not query my ActiveRecord relation? - ruby

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?

Related

How to order by latest nested model created_at in a query

I've got a question, I don't know if this is possible but suppose these models
class Event < ApplicationRecord
has_many :gifs
end
class Gif < ApplicationRecord
belongs_to :event
end
blank database using rails console you do this
gif_1 = Gif.create
gif_2 = Gif.create
gif_3 = Gif.create
event_1 = Event.new
event_1.gifs = [gif_1, gif_3]
event_1.save
event_2 = Event.new
event_2.gifs = [gif_2]
event_2.save
How would you order events by their last gifs created's created_at attribute.
Here is an example of what I've tried but that doesn't produce the correct result
ordered_events = Event.includes(:gifs).joins(:gifs).order("gifs.created_at DESC")
ordered_events.first.id
=> 2 # I want this to return 1
Now I understand why my attempt probably didn't work. I think it's because it probably only looked at the first Gif to do the ordering.
On top of this I had another thought, and here I have no idea where to begin trying to do this in a query, but what if the Event has 0 Gif, from what I wrote it seems no gifs simply relegates those events to after the ones who do have gifs but this would not work for me.
here's another context in rails console which is more realistic since normally you'd need an event first to store the Gif
event_1 = Event.create
event_2 = Event.create
gif_1 = Gif.create(event_id: event_1.id)
gif_2 = Gif.create(event_id: event_2.id)
event_1 = Event.create
gif_3 = Gif.create(event_id: event_1.id)
Now here what I would like to get back from my query would be something of the sorts of [event_1, event_3, event_2] because since event_3 has no gifs I want to use his created_at to order.
I know how I could do this by hand via some helper function or other but I would really love to be able to this kind of thing in one query directly.
As an example:
Event.joins(:gifs)
.group('events.id')
.order('MAX(gifs.created_at) DESC')
This query takes events, joins them with gifs, groups by event.id (to eliminate event duplicates in case one event has several gifs) and sorts result by latest (maximal) created_at time of event's gifs, in descending order.
Since it uses joins method, which unwraps to INNER JOIN in SQL query, events without gifs won't be returned by this query. To fix it use left_outer_joins:
Event.left_outer_joins(:gifs)
.group('events.id')
.order('MAX(gifs.created_at) DESC')

Creating a scope on an ActiveRecord Model

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

ActiveRecord: Find through multiple instances

Say I have the following in my controller:
#category1
#category2
and I want to find all stores associated with those two categories...
#stores = #category1.stores + #category2.stores
this does work, but unfortunately returns an unaltered Array, rather than a AR::Base Array, and as such, I can't do things like pagination, scope, etc...
It seems to me like there's a built-in way of finding through multiple instance association... isn't there?
##stores = #category1.stores + #category2.stores
#if you want to call API methods you can just add conditions with the category id
#stores = Store.find(:all, :conditions => ['category_id=?', a || b])
With ActiveRecord, whenever you're finding a set of unique model objects, calling find on that model is usually your best bet.
Then all you need to do is constrain the join table with the categories you care about.
#stores = Store.all(:joins => :categories,
:conditions => ['category_stores.category_id in (?)', [#category1.id, #category2.id]])

how to say 'is not in' in an active record query

Hey I hope you can help me,
#courses = Course.where(:id != current_user.courses)
I want to get the courses, where the current user is not registered to
#courses = Course.where(:id => current_user.courses) gives me the courses where the user has registered to. But I want the opposite
Associations are just fine. I can use current_user.courses or current_user.assignments.
You probably need something like this:
#courses = Course.where("id NOT IN (?)", current_user.courses)
The reason you can't use a pure array or hash condition is because you're negating ("NOT IN") the query. As far as I know, this can't be done without using the string based syntax. If you just wanted to find matching ("IN") items, you could use the hash form:
#courses = Course.where(:id => current_user.courses)
It's the negating ("NOT IN") part that makes it tricky.
More information can be found in the Active Record Query Interface Guide.
If you wish to avoid using raw strings, you could use ARel
#courses = Course.where(Course.arel_table[:id].not_in(current_users.courses))
Unfortunately, ARel is not documented very well. I use this as a reference.

Ruby: DataMapper and has n, :xyz, :through => Resource

I've encountered following issue:
there are 2 models: X and Y, they're associated with each other like this: has n, :<name>, :through => Resouce; when i'm doing something like x.ys = array_with_500_ys it takes really long time because DataMapper inserts only one association per query (insert into xs_ys(x_id, y_id) values(xid, yid)). This takes really long.
The question is: how to make this faster?
Thanks.
Because DataMapper has abstracted the 'back end', the standard behaviour is to insert one record at a time as SQL (or whatever you are using).
Assuming you are using an SQL backend, such as Postgres, you could drop back to raw SQL, and do the following:
x = X.first
query = "INSERT INTO xs_ys(x_id, y_id) VALUES"
vals = []
array_with_500_ys.each do |y|
vals << "(#{x.id}, #{y.id})"
end
repository.adapter.execute(query + vals.join(','));
This creates one 'insert', passing all records to be inserted. Not sure if this would be any faster, but you could put it into a background job if you need the app not to time out for the user.

Resources