LIKE in .where with audited gem - ruby-on-rails

I would like find audited changes of state but i dont know who find audited changes from any states
I try this and its work but i would like find last audited change from any states to offboarded:
product.audits.where("audited_changes LIKE '%state:%published%offboarded%'").last
I would like something like this: LIKE '%state:%ANY WORD%offboarded%'
audited change format:
audited_changes: {"state"=>["published", "offboarded"]}
I want find this result too for example:
audited_changes: {"state"=>["hidden", "offboarded"]}
I use Rails with postgresql

I think you can use the #> operator ("contains" operator) in your where clause:
product.audits.where("audited_changes #> '{\"state\": [\"published\", \"offboarded\"]}'")
Docs for different data types:
For arrays: https://www.postgresql.org/docs/current/functions-array.html
For JSON: https://www.postgresql.org/docs/current/functions-json.html

Related

activerecord not like query

I could not find an activerecord equivalent of "Not Like". I was able to find a where.not, but that will check if a string does not match a value, as so:
User.where.not(name: 'Gabe')
is the same as:
User.where('name != ?', 'Gabe')
I was looking for a NOT LIKE, where the value is not contained in the string. The equivalent sql query would look like as follows:
SELECT * FROM users WHERE name NOT LIKE '%Gabe%'
In ActiveRecord I can currently get away with the following:
User.where("name NOT LIKE ?", "%Gabe%")
But that leaves a lot to be desired. Any new additions to Rails 4 to facilitate this?
Well, you can do something like:
User.where.not("name LIKE ?", "%Gabe%")
Note: This is only available in Rails 4.
As others have pointed out ActiveRecord does not have a nice syntax for building like statements. I would suggest using Arel as it makes the query less database platform specific (will use ilike for sqlite & like for other platforms).
User.where(User.arel_table[:name].does_not_match('%Gabe%'))
You could also implement this as a scope to contain the implementation to the model:
class User < ActiveRecord::Base
scope :not_matching,
-> (str) { where(arel_table[:name].does_not_match("%#{str}%")) }
end
Unfortunately ActiveRecord does not have a like query builder. I agree that the raw 'NOT LIKE' leaves a lot to be desired; you could make it a scope (scope :not_like, (column, val) -> { ... }), but AR itself does not do this.
Just addition to the answer of "where.not" of active record. "where.not" will exclude null values also. i.e. Query User.where.not(name: 'Gabe') will leave record with name 'Gabe' but it also exclude name column with NULL values. So in this scenario the solution would be
User.where.not(name: 'Gabe')
.or(User.where(name: nil))

how to use LIKE in included relations

I want to use search condition with included relations, just like below
Post.includes(:tags).where( tags: { title: '%token%' }).all
The posts and tags table has been associated with a 3rd table named post_tag_relations.
The schema is like below:
posts
id: pk
title: string
content: text
tags
id: pk
title: string
post_tag_relations
id: pk
tag_id: integer
post_id: integer
The syntax only works with equal condition, I really dont know how to use LIKE search condition.
When using Post.joins(:tags) and Tag.area_table[:title].matches('%token%') it will works fine, but some post that has no tags will not be fetch out.
Could anyone help me? Thanks a lot.
UPDATE:
The Rails version is 4.1.
I want to search the post like posts.title LIKE '%token%' OR tags.title LIKE '%token%', so if use Post.joins(:tags) will not be functional if some posts have no tags. So I need use Post.includes(:tags) instead.
UPDATED AGAIN:
looks cannot use one-query to fetch, so I had already try another database schema...
Why not do this:
Post.includes(:tags).where(Tag.arel_table[:title].matches('%token%').or(Tag.arel_table[:post_id].eq(nil)))
Since ruby-on-rails-2 the joins operation is used in all cases before the includes operation during performance, but since includes uses LEFT OUTER JOIN operator, you should use exactly it. May be you need also to use not LEFT, but FULL join. So try this with arel gem:
class Post
scope :with_token(token), -> do |token|
re = Regexp.union(token).to_s
cond = Arel.sql("title REGEXP ? OR content REGEXP ?", re, re)
includes(:tags).where(Tag.arel_table[:title].eq(token).or(cond))
end
end
Of course original condition could be replaced to use LIKE operator:
class Post
scope :with_token(token), -> do |token|
includes(:tags).where(arel_table[:title].matches("%#{token}%")
.or(arel_table[:content].matches("%#{token}%")
.or(Tag.arel_table[:title].eq(token))))
end
end
NOTE: If there are some errors, provide please result SQL.
Something like this:
Post.includes(:tags).where( "tags.title LIKE ?", "%#{token}%" )
could work.
(The syntax might be a little wrong, sorry, but you get the idea)

Finding values in Ruby Hashes that contain the ouput of a SQL select resultset

I am Learning ruby. I would like to use active record to use a simple find command like this: MyModel.find_by_emp_id(i.emp_id) on the model but I cant with the Vertica database gem I am using. The resultset of running a straight SQL query on the model like this:
vemployees = conn.query("select * from employees") returns a hash like data structure.
The data structure vemployees is a Vertica::result type, and the structure looks to be like bellow:
[
{:emp_id=>"3321", :emp_last_name=>"Man", :emp_first_name=>"super", :emp_mid_name=>nil},
{:emp_id=>"3325", :emp_last_name=>"Man", :emp_first_name=>"Bat", :emp_mid_name=>nil},
]
How can I execute something like, vemployees.find_by_emp_id(i.emp_id) without going through a list of results?
N.B. untested code
something like:
in MyModel:
def self.find_by_emp_id(id)
conn.query("select * from employees where emp_id = #{id}")[0]
end
Since the gem is not ActiveRecord you'll have to write your own accessors, you cannot rely on the automagically created ActiveRecord find_by_ methods. I think you can hack that gem into ActiveRecord and have all of ActiveRecord's goodness but that's probably more than you'd want to take on as a Ruby beginner.
Be careful to sanitize any user input ...

Convert ActiveRecord habtm query to Arel

I have a pretty common habtm relationship:
Photo has_and_belongs_to_many :tags
Tag has_and_belongs_to_many :photos
In my Photo model I've got a method "with tags" that I use to find a photo that is tagged with a given set of tag_ids. This query needs to match only photos that have all of the given tags, but disregarding the presence or lack of any other tags. Here's my method:
def self.with_terms( array )
select('distinct photos.*').joins(:tags).where('tags.id' => array).group("photos." + self.column_names.join(', photos.')).having("count(*) = #{array.size}")
end
This works as expected.
Now, in order to integrate this better with some other libraries I'm using, I need to re-write this in Arel. (make it an Arel node?, not sure what you normally call this).
I've been experimenting with this, but to be honest I've never tried to use Arel before, so I'm a little lost. I've been experimenting in the console and tried:
t = Photo.arel_table
q = t.join(:tags).on(t[:tags_id].in(array))
Photo.where(q)
But, (1) I don't think q is the right query in the first place, and (2) it creates an Arel::SelectManager, which when passed to a where call raises Cannot visit Arel::SelectManager. So, obviously I'm doing this wrong.
Update: Just to be extra-specific here, I'm looking to return an Arel node, because I'm working with a gem (ransack) that expects you to pass it Arel nodes for search methods. Ransack will chain this Arel node with others in generating complex search queries.
Could an Arel guru show me how do this correctly?
It's hard to find good Arel documentation, but #Philip C has put together some useful slides, referenced in his answer to this question.
The following should be what you're looking for:
photos = Arel::Table.new(:photos)
tags = Arel::Table.new(:tags)
photo_tags = Arel::Table.new(:photo_tags)
q = photos[:id].in(
photos.project(photos[:id])
.join(photo_tags).on(photos[:id].eql(photo_tags[:photo_id]))
.join(tags).on(photo_tags[:tag_id].eql(tags[:id]))
.where(tags[:id].in(array))
.group(photos.columns)
.having(tags[:id].count.eq(array.length))
)
This results in an Arel::Nodes::In instance that you should be able to use directly as in Photo.where(q).
UPDATE:
After looking through the documentation and some of the source for ransack, there doesn't seem to be any natural way to define a custom predicate involving a subquery, which is necessary in your case (because predicates must fit into a where clause). One way to work around this might be to take advantage of the :formatter that your predicate uses as follows:
Ransack.configure do |config|
config.add_predicate 'with_tag_ids',
:arel_predicate => 'in',
:formatter => proc {|tag_ids| tags_subquery(tag_ids) },
:validator => proc {|v| v.present?},
:compounds => true
end
You can define tags_subquery(tag_ids) as a method that generates the arel node as above but replaces array with tag_ids and calls .to_sql on it before returning it (the formatter needs to return a string, not a node).
I haven't tried this, so I'll be thrilled if it works!

Postgres accent insensitive LIKE search in Rails 3.1 on Heroku

How can I modify a where/like condition on a search query in Rails:
find(:all, :conditions => ["lower(name) LIKE ?", "%#{search.downcase}%"])
so that the results are matched irrespective of accents? (eg métro = metro). Because I'm using utf8, I can't use "to_ascii". Production is running on Heroku.
Proper solution
Since PostgreSQL 9.1 you can just:
CREATE EXTENSION unaccent;
Provides a function unaccent(), doing what you need (except for lower(), just use that additionally if needed). Read the manual about this extension.
More about unaccent and indexes:
Does PostgreSQL support "accent insensitive" collations?
Poor man's solution
If you can't install unacccent, but are able to create a function. I compiled the list starting here and added to it over time. It is comprehensive, but hardly complete:
CREATE OR REPLACE FUNCTION lower_unaccent(text)
RETURNS text
LANGUAGE sql IMMUTABLE STRICT AS
$func$
SELECT lower(translate($1
, '¹²³áàâãäåāăąÀÁÂÃÄÅĀĂĄÆćčç©ĆČÇĐÐèéêёëēĕėęěÈÊËЁĒĔĖĘĚ€ğĞıìíîïìĩīĭÌÍÎÏЇÌĨĪĬłŁńňñŃŇÑòóôõöōŏőøÒÓÔÕÖŌŎŐØŒř®ŘšşșߊŞȘùúûüũūŭůÙÚÛÜŨŪŬŮýÿÝŸžżźŽŻŹ'
, '123aaaaaaaaaaaaaaaaaaacccccccddeeeeeeeeeeeeeeeeeeeeggiiiiiiiiiiiiiiiiiillnnnnnnooooooooooooooooooorrrsssssssuuuuuuuuuuuuuuuuyyyyzzzzzz'
));
$func$;
Your query should work like that:
find(:all, :conditions => ["lower_unaccent(name) LIKE ?", "%#{search.downcase}%"])
For left-anchored searches, you can use an index on the function for very fast results:
CREATE INDEX tbl_name_lower_unaccent_idx
ON fest (lower_unaccent(name) text_pattern_ops);
For queries like:
SELECT * FROM tbl WHERE (lower_unaccent(name)) LIKE 'bob%';
Or use COLLATE "C". See:
PostgreSQL LIKE query performance variations
Is there a difference between text_pattern_ops and COLLATE "C"?
For those like me who are having trouble on add the unaccent extension for PostgreSQL and get it working with the Rails application, here is the migration you need to create:
class AddUnaccentExtension < ActiveRecord::Migration
def up
execute "create extension unaccent"
end
def down
execute "drop extension unaccent"
end
end
And, of course, after rake db:migrate you will be able to use the unaccent function in your queries: unaccent(column) similar to ... or unaccent(lower(column)) ...
First of all, you install postgresql-contrib. Then you connect to your DB and execute:
CREATE EXTENSION unaccent;
to enable the extension for your DB.
Depending on your language, you might need to create a new rule file (in my case greek.rules, located in /usr/share/postgresql/9.1/tsearch_data), or just append to the existing unaccent.rules (quite straightforward).
In case you create your own .rules file, you need to make it default:
ALTER TEXT SEARCH DICTIONARY unaccent (RULES='greek');
This change is persistent, so you need not redo it.
The next step would be to add a method to a model to make use of this function.
One simple solution would be defining a function in the model. For instance:
class Model < ActiveRecord::Base
[...]
def self.unaccent(column,value)
a=self.where('unaccent(?) LIKE ?', column, "%value%")
a
end
[...]
end
Then, I can simply invoke:
Model.unaccent("name","text")
Invoking the same command without the model definition would be as plain as:
Model.where('unaccent(name) LIKE ?', "%text%"
Note: The above example has been tested and works for postgres9.1, Rails 4.0, Ruby 2.0.
UPDATE INFO
Fixed potential SQLi backdoor thanks to #Henrik N's feedback
There are 2 questions related to your search on the StackExchange:
https://serverfault.com/questions/266373/postgresql-accent-diacritic-insensitive-search
But as you are on Heroku, I doubt this is a good match (unless you have a dedicated database plan).
There is also this one on SO: Removing accents/diacritics from string while preserving other special chars.
But this assumes that your data is stored without any accent.
I hope it will point you in the right direction.
Assuming Foo is the model you are searching against and name is the column. Combining Postgres translate and ActiveSupport's transliterate. You can do something like:
Foo.where(
"translate(
LOWER(name),
'âãäåāăąÁÂÃÄÅĀĂĄèééêëēĕėęěĒĔĖĘĚìíîïìĩīĭÌÍÎÏÌĨĪĬóôõöōŏőÒÓÔÕÖŌŎŐùúûüũūŭůÙÚÛÜŨŪŬŮ',
'aaaaaaaaaaaaaaaeeeeeeeeeeeeeeeiiiiiiiiiiiiiiiiooooooooooooooouuuuuuuuuuuuuuuu'
)
LIKE ?", "%#{ActiveSupport::Inflector.transliterate("%qué%").downcase}%"
)

Resources