Rails 3.0 with "or" statement in each loop - ruby-on-rails

I am trying to build a query that compares two object, and if they have the same id the record is not fetched. What I have is this:
#channels.each do |channel|
unless #guide_channels.where(:id => channel.id).exists?
#dropdown_channels = #dropdown_channels.where(:id => channel.id)
end
end
This creates the query, but puts a AND between every value which isn't what I have in mind. I want the "or" operator. Is there a 'orwhere' function I can use or is there a better way to do this with some compare function?

The point is that the .where() method of a AR::Relation objects adds the condition to a set of conditions that are then AND-ed together when the query is executed.
What you have to do is a query like NOT IN:
# select all the ids for related #guide_channels
# if #channels comes from a query (so it's a ActiveRecord::Relation):
guide_ids = #guide_channels.where(:id => #channels.pluck(:id)).pluck(:id)
# use .where(:id => #channels.map {|c| c.id}) instead if #channels is just an array
# then use the list to exclude results from the following.
#dropdown_channels = #dropdown_channels.where("id NOT IN (?)", guide_ids.to_a)
The first query will accumulate all the IDs for channels that have an entry in #guide_channels. The second one will use the result of the first to exclude the found channels from tthe results for the dropdown.

This strange behavior is due to ActiveRecord's lazy evaluation of scopes.
What happens is that the line
#dropdown_channels = #dropdown_channels.where(:id => channel.id)
Does not send a query to the DB until you actually use the value of #dropdown_channels, and when you do it concatenates all the conditions into one big query, this is why you get the AND between the conditions.
In order to force ActiveRecord to eager load the scope you can use either the all scope or the first scope, for example:
#dropdown_channels = #dropdown_channels.where(:id => channel.id).first
This will force ActiveRecord to calculate the query on spot returning the result immediately and not to accumulate the scopes for lazy evaluation.
Another approach could be to accumulate all those channels_ids and get them later in one query, instead of making a query for each one. This approach is more cost-effective relating to DB resources.
In order to achieve this :
dropdown_channels_ids = []
#channels.each do |channel|
unless #guide_channels.where(:id => channel.id).exists?
dropdown_channels_ids << channel.id
end
end
#dropdown_channels = #dropdown_channels.where(:id => dropdown_channels_ids)

Related

.includes with .order does not use single query

With this query:
cases = Case.includes(:arrangement)
I can loop through all the cases and get arrangement values without more queries being generated every time I call case.arrangement.id
With this query:
cases = Case.includes(:arrangement).order('arrangements.id asc')
As i loop through all the cases and get arrangement values it runs another query to load the arrangement record for that case.
How can I use .includes along with ordering by an arrangement column and not have a separate query run every time I call case.arrangement.id?
Here's how I'm looping after I run the query:
cases_array = []
cases.each_with_index do |caseObj, i|
json_case = caseObj.as_json
json_case = json_case.merge(
:arrangement_id => caseObj.arrangement.try(:id),
:arrangement_location => caseObj.arrangement.try(:location).try(:name),
:arrangement_datetime => view_context.spell_date_and_time(
caseObj.arrangement.try(:timeslot).try(:timeslot)))
cases_array[i] = json_case
puts json_case
end
Maybe you should add more in your include?
cases = Case.includes(arrangement: [:location, :timeslot]).order('arrangements.id asc')
You need to pass arrangements(table name) as references as well:
cases = Case.includes(:arrangement).order('arrangements.id asc').references(:arrangements)

building a dynamic where clause in model with ruby, iterating over activerecord object

I have a model with an has_many association: Charts has_many ChartConditions
charts model has fields for:
name (title)
table_name (model)
The chart_conditions model has fields for
assoc_name (to .joins)
name (column)
value (value)
operator (operator
Basically my Chart tells us which model (using the table_name field) I want to run a dynamic query on. Then the chart_conditions for the Chart will tell us which fields in that model to sort on.
So In my models that will be queried, i need to dynamically build a where clause using multiple chart_conditions.
Below you can see that i do a joins first based on all the object's assoc_name fields
Example of what I came up with. This works, but not with a dynamic operator for the name/value and also throws a deprecation warning.
def self.dynamic_query(object)
s = joins(object.map{|o| o.assoc_name.to_sym})
#WORKS BUT GIVES DEPRECATED WARNING (RAILS4)
object.each do |cond|
s = s.where(cond.assoc_name.pluralize.to_sym => {cond.name.to_sym => cond.value})
end
end
How can i then add in my dynamic operator value to this query? Also why can I not say:
s = s.where(cond.assoc_name.pluralize : {cond.name : cond.value})
I have to use the => and .to_sym to get it to work. The above syntax errors: syntax error, unexpected ':' ...ere(cond.assoc_name.pluralize : {cond.name : cond.value}) ... ^
What if you store the query in a variable and append to that?
def self.dynamic_query(object)
q = joins(object.map{|o| o.assoc_name.to_sym})
object.each do |cond|
q = q.where(cond.assoc_name.pluralize : {cond.name : cond.value})
end
q # returns the full query
end
Another approach might be the merge(other) method. From the API Docs:
Merges in the conditions from other, if other is an ActiveRecord::Relation. Returns an array representing the intersection of the resulting records with other, if other is an array.
Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# Performs a single join query with both where conditions.
That could be useful to knot all the conditions together.

Are the .order method parameters in ActiveRecord sanitized by default?

I'm trying to pass a string into the .order method, such as
Item.order(orderBy)
I was wondering if orderBy gets sanitized by default and if not, what would be the best way to sanitize it.
The order does not get sanitized. This query will actually drop the Users table:
Post.order("title; drop table users;")
You'll want to check the orderBy variable before running the query if there's any way orderBy could be tainted from user input. Something like this could work:
items = Item.scoped
if Item.column_names.include?(orderBy)
items = items.order(orderBy)
end
They are not sanitized in the same way as a .where clause with ?, but you can use #sanitize_sql_for_order:
sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
# => "field(id, 1,3,2)"
sanitize_sql_for_order("id ASC")
# => "id ASC"
http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_for_order
Just to update this for Rails 5+, as of this writing, passing an array into order will (attempt to) sanitize the right side inputs:
Item.order(['?', "'; DROP TABLE items;--"])
#=> SELECT * FROM items ORDER BY '''; DROP TABLE items;--'
This will trigger a deprecation warning in Rails 5.1 about a "Dangerous query method" that will be disallowed in Rails 6. If you know the left hand input is safe, wrapping it in an Arel.sql call will silence the warning and, presumably, still be valid in Rails 6.
Item.order([Arel.sql('?'), "'; DROP TABLE items;--"])
#=> SELECT * FROM items ORDER BY '''; DROP TABLE items;--'
It's important to note that unsafe SQL on the left side will be sent to the database unmodified. Exercise caution!
If you know your input is going to be an attribute of your model, you can pass the arguments as a hash:
Item.order(column_name => sort_direction)
In this form, ActiveRecord will complain if the column name is not valid for the model or if the sort direction is not valid.
I use something like the following:
#scoped = #scoped.order Entity.send(:sanitize_sql, "#{#c} #{#d}")
Where Entity is the model class.
Extend ActiveRecord::Relation with sanitized_order.
Taking Dylan's lead I decided to extend ActiveRecord::Relation in order to add a chainable method that will automatically sanitize the order params that are passed to it.
Here's how you call it:
Item.sanitized_order( params[:order_by], params[:order_direction] )
And here's how you extend ActiveRecord::Relation to add it:
config/initializers/sanitized_order.rb
class ActiveRecord::Relation
# This will sanitize the column and direction of the order.
# Should always be used when taking these params from GET.
#
def sanitized_order( column, direction = nil )
direction ||= "ASC"
raise "Column value of #{column} not permitted." unless self.klass.column_names.include?( column.to_s )
raise "Direction value of #{direction} not permitted." unless [ "ASC", "DESC" ].include?( direction.upcase )
self.order( "#{column} #{direction}" )
end
end
It does two main things:
It ensures that the column parameter is the name of a column name of the base klass of the ActiveRecord::Relation.
In our above example, it would ensure params[:order_by] is one of Item's columns.
It ensures that the direction value is either "ASC" or "DESC".
It can probably be taken further but I find the ease of use and DRYness very useful in practice when accepting sorting params from users.

find :all conditions for hash

In my database I have the table 'audits' which contains a field called 'changes' which stores data in the form of hash.
I would like to retrieve all audits with the following conditions:
- auditable_type = 'Expression'
- action = 'destroy'
- changes = :EXP_SUBMISSION_FK =>'9999992642' #note that this field stores hash values
#expression_history_deleted = Audit.find(:all, :conditions => ["auditable_type =? AND action = ? AND changes = ?",'Expression', 'destroy' , { :EXP_SUBMISSION_FK =>'9999992642'} ])
The code above return nothing.
If I remove 'changes' in the condition so that it reads as follows, I get a list of entries:
#expression_history_deleted = Audit.find(:all, :conditions => ["auditable_type =? AND action = ?",'Expression', 'destroy'])
<% #expression_history_deleted.each do |test| %>
<%= test.changes['EXP_SUBMISSION_FK'] %> ---displays the value
<% end %>
My question is how do I perform a search and set the conditions for the hash value.
****
I am using 'acts_as_audited' which stores all the changes in hash format in the 'audits' table.
****
I assume you're using the serialize function in ActiveRecord?
Unfortunately, I don't think it's easily possible to search it on the SQL level. It's obviously possible to pull out all of the records and handle the search in Ruby, but that's less than ideal.
I'd recommend moving anything you need to search on out of the serialized column and into its own.
If you are using ActiveRecord serialize macros, then you may dump containment of column changes and separete constructions like "audible_type: Expression". Try performing a series of fulltext search on that column and the work is done.
But more preferable way is to extract that data into real database level colums

rails - activerecord ... grab first result

I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"

Resources