find :all conditions for hash - ruby-on-rails

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

Related

How to use raw Postgres SQL query result in Ruby on Rails 5.2?

My application manages several translations of a field as nested fields in a form.
Initially, I used
f.fields_for fields, translations do |locution| to initialise the nested fields (which works fine).
Now I need to execute a more complex query to get the list of translations, and also allow the user to create new translations for each available language. So I modified the code as:
<% raw_SQL = "select #{document_id} as document_id, '#{document_type}' as document_type, '#{field_name}' as field_name, property as language,
case when property = language then existing_translations.id else null end as id,
case when property = language then existing_translations.translation else null end as translation
from
(select * from dqm_app.translations where document_id = #{document_id} and field_name = '#{field_name}' and document_type = '#{document_type}') as existing_translations
right outer join (select property
from dqm_app.parameters inner join dqm_app.parameters_lists on parameters.parameters_list_id = parameters_lists.id
where parameters_lists.CODE = 'LIST_OF_LANGUAGES') as langues on property = language" %>
<% f.fields_for fields, ActiveRecord::Base.connection.exec_query(raw_SQL).to_a do |locution| %>
Translation exists for French only, other translations can be added by user thanks to the nested fields to be created:
The query returns the expected result, but in the form of array of hashes instead of an ActiveRecord collection. How can I make this result usable for the fields_for method?
Thanks a lot for your help!
You can use a model that has these attributes and cast the results yourself.
records = ActiveRecord::Base.connection.exec_query(raw_sql).to_a
records.map { |attrs| YourModel.new(attrs) }
This would work because Rails allows you to initialize a model through a hash like
YourModel.new({ id: 1 })
Map will go over each element in the array and initialize them individually.

Rails 3.0 with "or" statement in each loop

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)

Given an Object form a query with 100s of items. How can I find an item in the object w/o having to re-query the db?

Given an object like contacts:
Contact.rb (id, fname, lname, key_tag)
#contacts = Contacts.where(:user_id => #user.id)
Given #contacts comes back with 1000s of records in that one DB query. How can I then get/see if a object exists in #contacts that matches a given 'key_tag'.
I tried:
#contacts.where(:key_tag => 'def12')
But that requeries the database which is exactly what I want to avoid. Ideas? Thanks
what you Have is an Relation there. So adding the other where changes the relation and queries that data back. It should be lazily loaded. So in your code
Contact.rb (id, fname, lname, key_tag)
#contacts = Contacts.where(:user_id => #user.id)
If you don't touch #contacts, it shouldn't even hit the DB. But once you try to get data from it, it will then be executed and data comes back. If you are going to show all of them, you could use something like #all to return them as an array, and then use Array#select to search through it. Depending upon the speed of the system and/or your db, it might be more efficient to the DB do the select.
#contacts = Contacts.where(:user_id => #user.id).all
#contacts_with_key = #contacts.select { |c| c.key_tag == 'def12' }
You can use Enumerable find method:
#contacts.find {|c| c.key_tag == 'def12'}
Try Enumerable#find_all
#contacts.find_all {|a| a[:key_tag] == 'def12'}
Or if you want to only find first one(given that key_tag is unique)
#contacts.find {|a| a[:key_tag] == 'def12'}
be sure to:
include Enumerable

Postgres ORDER BY values in IN list using Rails Active Record

I receive a list of UserIds(about 1000 at a time) sorted by 'Income'. I have User records in "my system's database" but the 'Income' column is not there. I want to retrieve the Users from "my system's database"
in the Sorted Order as received in the list. I tried doing the following using Active Record expecting that the records would be retrieved in the same order as in the Sorted List but it does not work.
//PSEUDO CODE
User.all(:conditions => {:id => [SORTED LIST]})
I found an answer to a similar question at the link below, but am not sure how to implement the suggested solution using Active Record.
ORDER BY the IN value list
Is there any other way to do it?
Please guide.
Shardul.
Your linked to answer provides exactly what you need, you just need to code it in Ruby in a flexible manner.
Something like this:
class User
def self.find_as_sorted(ids)
values = []
ids.each_with_index do |id, index|
values << "(#{id}, #{index + 1})"
end
relation = self.joins("JOIN (VALUES #{values.join(",")}) as x (id, ordering) ON #{table_name}.id = x.id")
relation = relation.order('x.ordering')
relation
end
end
In fact you could easily put that in a module and mixin it into any ActiveRecord classes that need it, since it uses table_name and self its not implemented with any specific class names.
MySQL users can do this via the FIELD function but Postgres lacks it. However this questions has work arounds: Simulating MySQL's ORDER BY FIELD() in Postgresql

Rails: combining optional params into a query

I have a view with a huge paginated list of records that need filtering.
Users can filter by records a few different ways (such as 'saved' records, 'read' records, and 'mark deleted' records) and I'd like for them to be able to combine these filters any possible way.
My current, flawed, non-functioning approach. The code below does not produce anything unless all of the params are specified and valid:
#view. Set the 'se' filter to true; leave all others as is
<%= link_to list_url(:id=>params[:id], :se=>"true", :st=>params[:st], :re=>params[:re]) do %>
<div class="button">Toggle SE</div>
<% end %>
#controller query. Add whichever params are passed into the conditions for the new page.
#query is paginated and sorted
#records = Record.where("user_id IN (?) AND see = ? AND star = ? AND delete = ? AND like = ?", #users.select("id"), params[:se], params[:st], params[:re]).paginate :page => params[:page], :order => (sort_column + " " + sort_direction)
What is the best way to create this filtering system?
I imagine a client-side sort would be faster than asking the server to become involved every time - is there a simple AJAX way to accomplish this kind of thing? Imagine filters the user can toggle on and off in any combination.
Try this:
conditions = {:user_id => #users.select("id")}
{
:se => :see,
:st => :star,
:del => :delete
}.each{|k1, k2| conditions[k2] = params[k1] unless params[k1].blank?}
#records = Record.where(conditions).paginate(...)
The conditions hash will be filled based on the values present in the params hash.
Edit 1
You can combine conditions hash and array.
#records = Record.where(conditions).where(
":created_at > ?", Date.today - 30).paginate(...)
You can change the user_id condition to what ever you want by specifying
conditions[:user_id] = #user.id
In the above statement, if the RHS is an array, rails automatically generates the IN clause. Otherwise, equality check(=) is performed.
Can also use Anonymous scopes: Combine arrays of conditions in Rails

Resources