ferret / acts_as_ferret : Specify that a field should be blank - ruby-on-rails

This relates to the lucene-based search engine, Ferret.
https://github.com/dbalmain/ferret
Let's say i have a model with two fields, myfield1 and myfield2. I want to get records that have myfield equal to "foo", or that have null (or an empty string) for myfield but have myfield2 set to "foo".
I DON'T want to get records that have, for example, myfield = "bar" and myfield2 = "foo". So, it's not as simple as just saying "myfield:foo || myfield2: foo" - i only want to look at myfield2 if myfield is empty.
The sql equivalent would be where (myfield = 'foo') or ((myfield is null or myfield = '') and myfield2 = 'foo'). What would the ferret search string equivalent of this be?
The following doesn't work, but it's an example of the sort of thing I'm after:
"myfield:foo || (myfield:<blank> && myfield2:foo)"
thanks, Max
BTW in case it's relevant i'm using acts_as_ferret in ruby on rails, but i think my question really just relates to a ferret search string. I'm using the ferret gem, v=0.11.6
EDIT: Slightly dirty-feeling solution below, would still like to know if it's possible just with the query string like above.
OK, i got around this by adding a new method, "myfield_blank":
def myfield_blank
myfield_blank?.to_s
end
then adding myfield_blank => {}, to my acts_as_ferret index definition. So now i can say
"myfield:foo || (myfield_blank:true && myfield2:foo)"
This works but like I say i'd still like to know if I can just do it in the query, without making new fields: this approach would be unacceptably hacky if i wanted to do it for lots of different fields. thanks

According to the source ?* should match any nonempty string, so you can try to do it this way:
'myfield:"foo" || (-myfield:"?*" && myfield2:"foo")'
And also I don't really see why 'myfield:"foo" || (myfield:"" && myfield2:"foo")' shouldn't work, but you probably already tried it...

Related

OR operator inside a Where Active Record Query

So inside a Where Active Record(AR) query you can do:
game_stickers.where('stickers.name != ?', 'Ban')
But how can you test matches against multiple strings with an OR operator without doing something like:
game_stickers.where('stickers.name != ? OR stickers.name != ?', 'Ban', 'Closed')
or without reverting to something like [see note below]:
game_stickers.where.not(stickers.name: ['Ban','Closed','Block'])
NOTE:
The reason I do not want to go with the last alternative is because I'm using some joins and references in my queries that (as far as I can see) do not play nicely with this option. The context code goes something like:
game_stickers_and_stickers = game_stickers.includes(:sticker)
game_stickers_and_stickers.where('stickers.name = ?', 'Ban').references(:stickers).where(placement_side: side)
Maybe a you can advise on the optimal way to do this query.
Note: it seems to me you want an AND between those conditions, not an OR. Think about it. Anyway, try this one
game_stickers_and_stickers = game_stickers.includes(:sticker)
game_stickers_and_stickers.where.not(stickers: {name: ['Ban','Closed','Block']}).where(placement_side: side)
that condition fragment should be converted to the SQL
WHERE stickers.name NOT IN ('Ban', 'Closed', 'Block')

Postgres LIKE in a rails application

I have a Postgres LIKE statement like this:
#favorites.find_each do |profiles|
#related_profiles = Profile.where('name LIKE ?', '%' + profiles.name + '%')
end
What I'm trying to do is loop through the favourites variable and find all profiles that contain some characters of the name.
For example a name "jasson jackson" should be found if the name contains "jackson" or "jasson"
The query you're looking for would be like:
Profile.where("name LIKE ?", "%#{profiles.name}%")
But note that your #related_profiles may not be properly assigned as the result would be the same as saying:
Profile.where("name LIKE ?", "%#{#favorites.last.name}%")
whereas, I doubt if that is what you need.
Also note that the it would be an ActiveRecord::Collection, an array like object.
A way to work around that is to initialize #related_profiles = [] and then at each point through your loop, you could do:
#related_profiles +=
Profile.where("name LIKE ?", "%#{profiles.name}%")
or another way is:
names = #favorites.map(&:name)
query = names.map{|name| "name LIKE '%#{name}%'"}.join(" OR ")
OR
query = #favorites.map{|favorite| "name LIKE '%#{favorite.name}%'" }.join(" OR ")
THEN
profiles = Profile.where(query)
UPDATE
Based on a comment from #joshrumbut, I decided to reimplement using the bind parameters.
However, code clarity is a bit lost, but here's a way it could be done:
names = #favorites.map(&:name)
query = names.map{|favorite| "name LIKE ?" }.join(" OR ")
profiles = Profile.where(query, *names.map{|name| ("%#{name}%")})
Based on the comment from #muistooshort, I removed the quotes from the first two queries and I think this approach looks a bit cleaner, as he suggested. From the docs
Profile.where('name like any(array[?])', names.map { |s| "%#{s}%" })
For better results with this type of search, You can try use FTS (Full text search).
Rails has a gem with this feature implemented:
https://github.com/Casecommons/pg_search
PgSearch builds named scopes that take advantage of PostgreSQL's full text search.
With this gem installed on your project, just use this statement for search:
PgSearch.multisearch("jasson")
Other option is Elasticsearch, with him you can index yours registers for a better text search.
Hope it helps :)
I'll try with Postgres patterns and "OR" operator - postgres doc
names = #favorites.map(&:name)
Profile.where('name LIKE %(?)%', names.join('|'))

Rails: Getting column value from query

Seems like it should be able to look at a simple tutorial or find an aswer with a quick google, but I can't...
codes = PartnerCode.find_by_sql "SELECT * from partner_codes where product = 'SPANMEX' and isused = 'false' limit 1"
I want the column named code, I want just the value. Tried everything what that seems logical. Driving me nuts because everything I find shows an example without referencing the actual values returned
So what is the object returned? Array, hash, ActiveRecord? Thanks in advance.
For Rails 4+ (and a bit earlier I think), use pluck:
Partner.where(conditions).pluck :code
> ["code1", "code2", "code3"]
map is inefficient as it will select all columns first and also won't be able to optimise the query.
You need this one
Partner.where( conditions ).map(&:code)
is shorthand for
Partner.where( conditions ).map{|p| p.code}
PS
if you are often run into such case you will like this gem valium by ernie
it gives you pretty way to get values without instantiating activerecord object like
Partner.where( conditions ).value_of :code
UPDATED:
if you need access some attribute and after that update record
save instance first in some variable:
instance=Partner.where( conditions ).first
then you may access attributes like instance.code and update some attribute
instance.update_attribute || instance.update_attributes
check documentation at api.rubyonrails.org for details

How to execute a raw update sql with dynamic binding in rails

I want to execute one update raw sql like below:
update table set f1=? where f2=? and f3=?
This SQL will be executed by ActiveRecord::Base.connection.execute, but I don't know how to pass the dynamic parameter values into the method.
Could someone give me any help on it?
It doesn't look like the Rails API exposes methods to do this generically. You could try accessing the underlying connection and using it's methods, e.g. for MySQL:
st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close
I'm not sure if there are other ramifications to doing this (connections left open, etc). I would trace the Rails code for a normal update to see what it's doing aside from the actual query.
Using prepared queries can save you a small amount of time in the database, but unless you're doing this a million times in a row, you'd probably be better off just building the update with normal Ruby substitution, e.g.
ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")
or using ActiveRecord like the commenters said.
ActiveRecord::Base.connection has a quote method that takes a string value (and optionally the column object). So you can say this:
ActiveRecord::Base.connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ
Note if you're in a Rails migration or an ActiveRecord object you can shorten that to:
connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{connection.quote(baz)}
EOQ
UPDATE: As #kolen points out, you should use exec_update instead. This will handle the quoting for you and also avoid leaking memory. The signature works a bit differently though:
connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
UPDATE foo
SET bar = $1
EOQ
Here the last param is a array of tuples representing bind parameters. In each tuple, the first entry is the column type and the second is the value. You can give nil for the column type and Rails will usually do the right thing though.
There are also exec_query, exec_insert, and exec_delete, depending on what you need.
None of the other answers showed me how to use named parameters, so I ended up combining exec_update with sanitize_sql:
User.connection.exec_update(
User.sanitize_sql(
[
"update users set name = :name where id = :id and name <> :name",
{
id: 123,
name: 'My Name'
}
]
)
)
This works for me on Rails 5, and it executes this SQL:
update users set name = 'My Name' where id = 123 and name <> 'My Name'
You need to use an existing Rails model instead of User if you don't have that.
I wanted to use named parameters to avoid issues with the ordering when I use ? or $1/$2,etc. Positional ordering is kind of frustrating when I have more than a handful of parameters, but named parameters allow me to refactor the SQL command without having to update the parameters.
You should just use something like:
YourModel.update_all(
ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)
That would do the trick. Using the ActiveRecord::Base#send method to invoke the sanitize_sql_for_assignment makes the Ruby (at least the 1.8.7 version) skip the fact that the sanitize_sql_for_assignment is actually a protected method.
Sometime would be better use name of parent class instead name of table:
# Refers to the current class
self.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
For example "Person" base class, subclasses (and database tables) "Client" and "Seller"
Instead using:
Client.where(self.class.primary_key => id).update_all(created _at: timestamp)
Seller.where(self.class.primary_key => id).update_all(created _at: timestamp)
You can use object of base class by this way:
person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
Here's a trick I recently worked out for executing raw sql with binds:
binds = SomeRecord.bind(a_string_field: value1, a_date_field: value2) +
SomeOtherRecord.bind(a_numeric_field: value3)
SomeRecord.connection.exec_query <<~SQL, nil, binds
SELECT *
FROM some_records
JOIN some_other_records ON some_other_records.record_id = some_records.id
WHERE some_records.a_string_field = $1
AND some_records.a_date_field < $2
AND some_other_records.a_numeric_field > $3
SQL
where ApplicationRecord defines this:
# Convenient way of building custom sql binds
def self.bind(column_values)
column_values.map do |column_name, value|
[column_for_attribute(column_name), value]
end
end
and that is similar to how AR binds its own queries.
I needed to use raw sql because I failed at getting composite_primary_keys to function with activerecord 2.3.8. So in order to access the sqlserver 2000 table with a composite primary key, raw sql was required.
sql = "update [db].[dbo].[#{Contacts.table_name}] " +
"set [COLUMN] = 0 " +
"where [CLIENT_ID] = '#{contact.CLIENT_ID}' and CONTACT_ID = '#{contact.CONTACT_ID}'"
st = ActiveRecord::Base.connection.raw_connection.prepare(sql)
st.execute
If a better solution is available, please share.
In Rails 3.1, you should use the query interface:
new(attributes)
create(attributes)
create!(attributes)
find(id_or_array)
destroy(id_or_array)
destroy_all
delete(id_or_array)
delete_all
update(ids, updates)
update_all(updates)
exists?
update and update_all are the operation you need.
See details here: http://m.onkey.org/active-record-query-interface

named_scope -- Finding entries where the field is neither null nor empty/blank

I only want to find the records that aren't null or empty/blank, currently I have;
named_scope :in_gallery, :conditions => ["gallery IS NOT NULL"]
(gallery is a string) but if the user inputs then deletes a title then this empty string is included in the results.
to clarify I want to be able to select only the entries where the field has some text...
What about
named_scope :in_gallery, :conditions => ["gallery IS NOT NULL AND gallery != ''"]
?
I think J's named scope definition looks right, but beyond that, I'd question why you are using both NULL and the empty string to mean "not in a gallery". If you have control over all the inserts and updates to this database, I'd recommend picking one or the other (NULL seems more natural to me) and sticking with it. It'll simplify your code in places like, for example, this named scope, and allowing both can lead to some really annoying bugs if you forget to check one or the other.
A simple way to implement this would be to add a validation to disallow setting the gallery to the empty string, and then fix whatever breaks (hopefully you have tests). If you're exposing an API and need to allow empty galleries for backwards compatibility, you could set a before_save hook to change the empty string to nil.
The following covers both not null and not empty:
named_scope :in_gallery, :conditions => ["gallery <> ''"]
Or in rails 3+ you can do:
scope :in_gallery, where("gallery <> ''")

Resources