Ruby Rails Query In conditional - sql

I have this query that works but I would like to expand it so that I can check for multiple ids such that I pass in a vector of ids. [1,2,3,5] etc... I have tried using SQL IN with no luck.
EventType.find(3).events.all(:include => {:sheet => :rink}, :conditions => ["rinks.id = ?", 2])

You were on the right track with IN. Here's syntax that will work in Rails 3+:
EventType.find(3).events.where("id IN (?)", [1,2,3]).include(:sheet => :rink)
Improvement from a comment removes SQL entirely:
EventType.find(3).events.where(:id => [1,2,3]).include(:sheet => :rink)

Related

Making this ActiveRecord lookups more efficient

I want to get an array of all email addresses for users of certain service types.
Using a string of ActiveRecord has_many relations, I can get these like this:
affected_services = Service.where(service_type: 'black')
affected_accounts = affected_services.map {|s| s.account}
affected_emails = affected_accounts.map {|a| a.contact.email}
I know it would be a fairly simple SQL query, but I'd prefer to see if ActiveRecord can do it to keep database abstraction.
Is there a good ActiveRecord way to retrieve those results?
You could use :include to include children in the query.
Account.find(:all, :include => :contact, :conditions => {:service_id => Service.where(:service_type => 'black').map{|account| account.contact.email } })

Rails ActiveRecord Find with Date

I'm having a hard time figuring this out but how do I tell my finder statement to ignore the time of the Datetime field in the db?
def trips_leaving_in_two_weeks
Trip.find(:all, :conditions => ["depart_date = ?", 2.weeks.from_now.to_date])
end
I want depart_date to come back as just a date but it keeps returning the time as well and causing this equality not to work. Is there someway to just compare against the dates? Thanks
Edit
Here's the code I'm using now that works:
Trip.find(:all, :conditions => ["DATE(depart_date) = ?", 2.weeks.from_now.to_date])
Not sure which DB you're using but does this work?
"depart_date = DATE(?)"
I would use this approach:
Rails 3.x
Trip.where(
:depart_date => 2.weeks.from_now.beginning_of_day..2.weeks.from_now.end_of_day
)
Rails 2.x
Trip.all(
:conditions => {
:depart_date => 2.weeks.from_now.beginning_of_day..2.weeks.from_now.end_of_day
})
If you index the depart_date column this solution will be efficient as the query uses the index. This solution is DB neutral.
When calculated fields are used in a where clause, the performance degrades(unless there is a special index).

How best to work around PostgreSQL's stricter grouping

I am trying to convert from using MySQL to using PostgreSQL. I have this type of structure:
User(entity) -> Follow -> Business(entity) -> Story
The user needs to see all the news updates put out by the businesses they follow. The following query works great with MySQL because it simply shows all associated stories and groups by the story.id. Unfortunately, being that PostgreSQL is much more literal in the interpretation of the SQL standard, if I want to do the GROUP BY clause I need to ask for each field individually using the DISTINCT clause which.
Story.find(:all, :joins => { :entity => { :followers => :follower } }, :conditions => ['followers_follows.id = ?', 4],
:group => 'stories.id')
PostgreSQL spits out: "ERROR: column "stories.entity_id" must appear in the GROUP BY clause or be used in an aggregate function"
Having to specify each field individually seems inelegant. If anybody can give me a clean way to get the same result as MySQL without having to resort to getting duplicate fields (removing the group by) or having to specify each individual field with along with the DISTINCT clause, I'd appreciate it!
Thanks!
Well, I certainly wouldn't say that PostgreSQL's interpretation of the SQL standard is too strict. In fact, it's the other way around.
Here's a possible solution:
Story.all( :joins => { :entity => { :followers=> :follower } },
:conditions => ['followers_follows.id = ?', 4],
:group => Story.column_names.map { |c| "stories.#{c}" }.join(', ') )
But there are many alternative queries. I blogged about this a few weeks ago. Here's the post: http://awesomeful.net/posts/72-postgresql-s-group-by

In Rails, how can I return a set of records based on a count of items in a relation OR criteria about the relation?

I'm writing a Rails app in which I have two models: a Machine model and a MachineUpdate model. The Machine model has many MachineUpdates. The MachineUpdate has a date/time field. I'm trying to retrieve all Machine records that have the following criteria:
The Machine model has not had a MachineUpdate within the last 2 weeks, OR
The Machine model has never had any updates.
Currently, I'm accomplishing #1 with a named scope:
named_scope :needs_updates,
:include => :machine_updates,
:conditions => ['machine_updates.date < ?', UPDATE_THRESHOLD.days.ago]
However, this only gets Machine models that have had at least one update. I also wanted to retrieve Machine models that have had no updates. How can I modify needs_updates so the items it returns fulfills that criteria as well?
One solution is to introduce a counter_cache:
# add a machine_updates_count integer database column (with default 0)
# and add this to your Machine model:
counter_cache :machine_updates_count
and then add OR machine_updates_count = 0 to your SQL conditions.
However, you can also solve the problem without a counter cache by using a LEFT JOIN:
named_scope :needs_updates,
:select => "machines.*, MAX(machine_updates.date) as last_update",
:joins => "LEFT JOIN machine_updates ON machine_updates.machine_id = machines.id",
:group => "machines.id",
:having => ["last_update IS NULL OR last_update < ?", lambda{ UPDATE_THRESHOLD.seconds.ago }]
The left join is necessary so that you are sure you are looking at the most recent MachineUpdate (the one with MAX date).
Note also that you have to put your condition in a lambda so it is evaluated every time the query is run. Otherwise it will be evaluated only once (when your model is loaded on application boot-up), and you will not be able to find Machines that have come to need updates since your app started.
UPDATE:
This solution works in MySQL and SQLite, but not PostgreSQL. Postgres does not allow naming of columns in the SELECT clause that are not used in the GROUP BY clause (see discussion). I'm very unfamiliar with PostgreSQL, but I did get this to work as expected:
named_scope :needs_updates, lambda{
cols = Machine.column_names.collect{ |c| "\"machines\".\"#{c}\"" }.join(",")
{
:select => cols,
:group => cols,
:joins => 'LEFT JOIN "machine_updates" ON "machine_updates"."machine_id" = "machines"."id"',
:having => ['MAX("machine_updates"."date") IS NULL OR MAX("machine_updates"."date") < ?', UPDATE_THRESHOLD.days.ago]
}
}
If you can make changes in the table, then you can use the :touch method of the belongs_to association.
For instance, add a datetime column to Machine named last_machine_update. Thereafter in the belongs_to of MachineUpdate, add :touch => :last_machine_update. This will cause that field to become updated with the last time you either added or modified a MachineUpdate connected to that Machine, thus removing the need for the named scope.
Otherwise I would probably do it like Alex proposes.
I just ran into a similar problem. It's actually pretty simple:
Machine.all(
:include => :machine_updates,
:conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago])
If you were doing a named scope, just use lambdas to ensure that the date is re-calculated every time the named scope is called
named_scope :needs_updates, lambda { {
:include => :machine_updates,
:conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago]
} }
If you want to avoid returning all of the MachineUpdate records in your query, then you need to use the :select option to only return the columns you want
named_scope :needs_updates, lambda { {
:select => "machines.*",
:conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago]
} }

rails active record nuances and protecting against injection attacks

When I make a query...
is there any meaningful difference between using a find_by helper or not?
Are there any reasons I'm overlooking for opting for shorter lines of code when doing things like this?
Booking.find_all_by_user_id(1, :joins => :confirmation)
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = ?', 1] )
No, regarding injection attacks.
The find_by method should be safe. However the only killer mistake is to use user input directly inside your conditions param when using find method, like doing:
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = #{params[user_id]]}'] )
Of course the right one is the way you did it and find method will filter things up.
Booking.find(:all, :joins => :confirmation, :conditions => [ 'bookings.user_id = ?', params[user_id]] )
What you're looking for is in here:
http://guides.rubyonrails.org/security.html#sql-injection
AND
http://guides.rubyonrails.org/security.html#mass-assignment
Be sure to read both carefully.