Rails query objects by key value of hash saved to column? - mysql

I have 2 objects, Visitors and Events. Visitors have multiple Events. An event stores parameters like this...
#<Event id: 5466, event_type: "Visit", visitor_token: "c26a6098-64bb-4652-9aa0-e41c214f42cb", contact_id: 657, data: {"url"=>"http://widget.powerpress.co/", "title"=>"Home (light) | Widget"}, created_at: "2015-12-17 14:51:53", updated_at: "2015-12-17 14:51:53", website_id: 2>
As you can see, there is a serialized text column called data that stores a hash with more data.
I need to find out if a visitor has visited a certain page, which would be very simple if the url parameter were it's own column, or if the hash were an hstore column, however it wasn't originally set up that way and it's a part of the saved hash.
Here's my attempted rails queries...
visitor.events.where("data -> url = :value", value: 'http://widget.powerpress.co/')
visitor.events.where("data like ?", "{'url' => 'http://widget.powerpress.co/'}")
visitor.events.where("data -> :key LIKE :value", :key => 'url', :value => "%http://widget.powerpress.co/%")
How does one properly query postgres to find objects that have a hash that contains a key with a specific value?

I suspect you're not looking for the right string. It should be "url"=>"http://widget.powerpress.co/", so:
visitor.events.where("data like ?", '%"url"=>"http://widget.powerpress.co/"%')
Check the right value directly in DB.

If you are storing hash in a text column, try following:
visitor.events.select{|ve| eval(ve.data)["url"] == "http://widget.powerpress.co/"}
Hope, it helps!

It worked for me.
visitor.events.select { |n| n.data && n.data['url'] == "http://widget.powerpress.co/"}

Related

Rails i18n API Triple Dashes/Hyphens, Ellipsises and Newlines

I am using i18n API for a purpose. I seed a MySQL database with:
Translation.find_or_create_by(locale: 'en', key:'key1', value: 'value1')
However, after seed, the data is saved on database as:
locale: en
key: key1
value: --- value1\n...\n
All columns are varchar(255) and 'utf8_unicode_ci'.
On Rails i18n documentation, I could not find an explanation for this.
Because of that problem, I can not use find_or_create_by() method. It do/can not check the value column and adds duplicate entries.
Is there any solution for that?
Translate model:
Translation = I18n::Backend::ActiveRecord::Translation
if Translation.table_exists?
I18n.backend = I18n::Backend::ActiveRecord.new
I18n::Backend::ActiveRecord.send(:include, I18n::Backend::Memoize)
I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::Simple.new, I18n.backend)
end
What you're seing in your value column is the value serialized to YAML (that's done by the I18n::Backend::ActiveRecord::Translation); which is required, among other things, for pluralization.
#find_or_create_by doesn't work nicely when the value stored in the database needs serialization
To do a simple seed try:
Translation.create_with(value: 'value1').find_or_create_by(locale: 'en', key: 'key1')

Ruby using a date as key in a hash from MySQL

I have a hash which is the result of a .map method on a MySQL2::Result object which looks like this:
{#<Date: 2018-01-02 ((2458121j,0s,0n),+0s,2299161j)>=>"OL,BD,DM,WW,DG"}
{#<Date: 2018-01-03 ((2458122j,0s,0n),+0s,2299161j)>=>"KP,LW"}
{#<Date: 2018-01-04 ((2458123j,0s,0n),+0s,2299161j)>=>"LW,WW,FS,DG"}
{#<Date: 2018-01-05 ((2458124j,0s,0n),+0s,2299161j)>=>"OL,KP,BD,SB,LW,DM,AS,WW,FS,DG"}
{#<Date: 2018-01-06 ((2458125j,0s,0n),+0s,2299161j)>=>"OL,KP,BD,SB,LW,DM,AS,WW,FS,DG"}
I would like to pull the values (the two letter items) from the hash, by referencing with the key.
I have tried
puts hash_name["2018-01-06"]
puts hash_name['2018-01-06']
puts hash_name[Date.new(2018,1,6)]
puts hash_name["<Date: 2018-01-06 ((2458125j,0s,0n),+0s,2299161j)>"]
puts hash_name["#<Date: 2018-01-06 ((2458125j,0s,0n),+0s,2299161j)>"]
All return nothing or an error.
The hash is created by doing the following:
hash_name = #available_items.map do
|h| {h["tdate"] => h["items"] }
end
Is there something I can do during the creation of the hash, or now, to be able to easily pull the value out using e.g. can I convert it to some other date format like ISO format?
Thanks
I think your problem is that Enumerable#map doesn't do what you think it does. This:
hash_name = #available_items.map do
|h| {h["tdate"] => h["items"] }
end
will give you an array of single entry hashes, the individual hashes will map Dates to strings but the result looks like:
[
{ date1 => string1 },
{ date2 => string2 },
...
]
rather than:
{
date1 => string1,
date2 => string2,
...
}
as you're expecting. Switching to #each_with_object should take care of your problem:
hash_name = #available_items.each_with_object({}) do |row, h|
h[row['tdate']] = row['items']
end
You're close here, but you're generating an array of hashes, not a singular hash:
hash_name = #available_items.map do |i|
[ i["tdate"], i["items"] ]
end.to_h
This creates an array of key/value pair arrays, then converts them to a hash with the .to_h method.
You can also use group_by if your input data can be grouped neatly, like:
hash_name = #available_items.group_by do |i|
i['tdate']
end
Where that approach might be good enough if can deal with the output format. It's keyed by date.
Note that using symbol keys like :tdate and :items is usually preferable to string keys. It's worth trying to steer towards that in most cases where there'd otherwise be rampant repetition of those strings.
In the hopes that this may help others to do a similar thing, here is what I ended up doing.
I have a MySQL2::Result object as shown above, on which I run:
#available_hash = #available_items.map do |row|
[ row["tdate"], row["available"] ]
end.to_h
Having previously declared a start_date and an end_date I then select an available item from the list, at random to fill a new hash using the dates as keys:
$final_hash = Hash.new("")
for date in (start_date..end_date)
#available_today = #available_hash[date].to_s.split(",")
$final_hash[date] = random_item(#available_today)
date +=1;
end
Whilst I am sure there is probably a more elegant way of doing this, I am delighted that you have helped me to get this to work!
Obviously hash map is not suitable for a date as the key, hash map is more suitable for key as id, tag, etc. It should be a unique key.
Please provide more information about what you need to do with this hash map, for sure you can have some more clever data structure.
If you have an array with two keys (tdate, items) and you want to lookup for the date just use select:
result = available_items.select { |elem| elem['tdate'] === Date.new(2001,2,3) }
reference for '===' operator in Date class
http://ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html#method-i-3D-3D-3D

How to use ransack to search MySQL JSON array in Rails 5

Rails 5 now support native JSON data type in MySQL, so if I have a column data that contains an array: ["a", "b", "c"], and I want to search if this column contains values, so basically I would like to have something like: data_json_cont: ["b"]. So can this query be built using ransack ?
Well I found quite some way to do this with Arrays(not sure about json contains for hash in mysq). First include this code in your active record model:
self.columns.select{|column| column.type == :json}.each do |column|
ransacker "#{column.name}_json_contains".to_sym,
args: [:parent, :ransacker_args] do |parent, args|
query_parts = args.map do |val|
"JSON_CONTAINS(#{column.name}, '#{val.to_json}')"
end
query = query_parts.join(" * ")
Arel.sql(query)
end
end
Then assuming you have class Shirt with column size, then you can do the following:
search = Shirt.ransack(
c: [{
a: {
'0' => {
name: 'size_json_contains',
ransacker_args: ["L", "XL"]
}
},
p: 'eq',
v: [1]
}]
)
search.result
It works as follows: It checks that the array stored in the json column contains all elements of the asked array, by getting the result of each json contains alone, then multiplying them all, and comparing them to arel predicate eq with 1 :) You can do the same with OR, by using bitwise OR instead of multiplication.

Sorting users in Rails based on values in array attribute of user model

I have a User model which has an array inside of it. This array is used to store points the user has scored in various activities. It basically looks like this:
<ActiveRecord::Relation [#<User id: 1, fullname: "Kaja Sunniva Edvardsen", points: [0, 4170, 3860, 2504, 2971, 3859, 4346]>, #<User id: 2, fullname: "Alexander Lie Sr.", points: [0, 3273, 3681, 2297, 2748, 4202, 3477]>]>
I want to sort all Users by the different values in the points array to be able to create ranking list for each of the different activities, points[0], points[1], etc...
Sorting by points[1] should return Kaja first, 4170>3273, sorting by points[6] should put Alexander first, 4202>3859
How do I do this?
As far as I know, MySQL does not have an integrated array type.
Assuming you have a model like this:
class User < ActiveRecord::Base
# ...
serialize :points, Array
# ...
end
You cannot sort with order queries, but you can try another solution (less efficient), handling the resources as an array:
User.all.sort { |user1, user2| user2.points[1] <=> user1.points[1] }
Which will return an array instead of an ActiveRecord query. Also, bear in mind that this code will not handle nil values (i.e. What if an user only have 2 elements in points?).

SQLite3::SQLException: no such column: parameters.user:

I am saving a list of followed users to the db and then trying to get the records where the current user is a part of that list but keep getting this exception.
SQLite3::SQLException: no such column: parameters.user: SELECT "activities".* FROM "activities" WHERE "parameters"."user" = 3
This is a record in the db
=> #<PublicActivity::Activity id: 107, trackable_id: 16, trackable_type: "Shout", owner_id: 1, owner_type: "User", key: "shout.shout", parameters: {:user=>[3]}, recipient_id: nil, recipient_type: nil, created_at: "2015-10-20 21:44:41", updated_at: "2015-10-20 21:44:41", read: false>
These are the queries I've tried that give me this. current_user.id = 3
PublicActivity::Activity.where({'parameters.user' => current_user.id})
PublicActivity::Activity.where(parameters: {user: current_user.id})
So. How do I get the records where the current user is a part of that list of users? Also, would the I be able to use the same query in Mysql?
Might seem like a silly question, but is the parameters column defined as a serialized column in the model? With adapters like MySQL and (I believe) SQLite you can't query serialized fields. So If it's something that you'd like to query, you need to save it separately from the serialized field.
That being said, with PostgreSQL and their rails adapter, you can query serialized fields.
You should check out this SO question and this anser