Using Concat within a Rails Scope - mysql

I have a Contact model. It has the attributes: first_name and last_name. The user enters text in a search field with the prompt: Enter the contact's full name. Rails then needs to find all contact records LIKE the name entered.
Here is a picture of the contact records:
-If the user types in "JOE" then rails will return two records (because it is case insensitive)
-If the user types in "joe s" then rails will return two records
-If the user types in "doe" then rails will return one record.
#models/contact.rb
class Contact < ActiveRecord::Base
scope :by_entered_name, -> (full_name){where("CONCAT('first_name',' ','last_name') LIKE ?", full_name)}
end
Generated sql when I run Contact.by_entered_name("joe") in the rails console:
SELECT "contacts".* FROM "contacts" WHERE (CONCAT('first_name',' ','last_name') LIKE 'joe'
I am using mysql in case that detail is important. For this example app however, I am using sqlite, and it is throwing a syntax error. Ultimately what is most important is that I get this to work on mysql.
Update: It was expressed that my question was not clear. My question is:
How do I properly create a query which takes text entered by a user, and finds all contacts whose concatenated first_name and last_name are LIKE that submitted text by the user? I also need it to be case insensitive. My attempted scope above does not appear to work.

There are some quotes in there you don't need. And you need the wildcard % in the parameter. Also, ILIKE is needed to disregard the case with Postgres.
class Contact < ActiveRecord::Base
scope :by_entered_name, -> (full_name){where("CONCAT(first_name,' ',last_name) ILIKE ?", "%#{full_name}%")}
end

SQLite doesn't use the CONCAT function, it uses || as a concatenation operator. Swards' answer will get you going in MySQL.

Related

Why I am not getting result of this query ,in rails?

I am trying to get rows from mysql in rails by following query.I am trying first it on console.But this is not working,please help me.
name="vikash"
List=User.find_by_sql["SELECT * from users where name like ?",%#{name}%]
A small mistake in your query.
Space after find_by_sql and name interpolation should be done with double quote.
name = "vikash"
list = User.find_by_sql ["SELECT * from users where name like ?", "%#{name}%"]
Check below links for details
http://www.w3schools.com/sql/trysql.asp?filename=trysql_select_like
http://apidock.com/rails/ActiveRecord/Querying/find_by_sql
Hope this will help you...
Do not put variable directly into the conditions string will pass the variable to the database as-is. This means that it will be an unescaped variable directly from a user who may have malicious intent.
You can check in console by name = "vikash'" and query with the query shown by #sanju
User.find_by_sql("SELECT * from users where name like '%#{name}%'")
And see the difference how malicious characters are escaped by querying with
list = User.find_by_sql ["SELECT * from users where name like ?", "%#{name}%"]
For further information visit:
http://guides.rubyonrails.org/active_record_querying.html
https://railsguide.wordpress.com/2016/03/02/sanitizing-user-input-while-quering/
Try updating your find_by_sql to the following:
User.find_by_sql(["SELECT * from users where name like ?", "%#{name}%"])
use this code:
list= User.find_by_sql("SELECT * from users where name like '%#{name}%'")
Try this query
User.find_by_sql("SELECT * from users where name like '%#{name}%'")

Prompt user to input a variable in MySQL

At school, I believe I work with Oracle SQL Developer when writing SQL. And in this I can type:
SELECT Book_Title, Auth_ID
FROM book
WHERE Auth_ID = '&Enter ID';
This will then display a little message box where the user can enter an ID number to see all the books written by an author with that ID number.
I want to know if there is a way to do this in MySQL. I have looked and the nearest thing I can find is setting a variable before hand, which is not quite what I'm looking for:
SET #EnterID := 2;
select Book_Title, Auth_ID
from book
where Auth_ID = #EnterID;
The above statement in MySQL will return all the books with author ID of 2, but only because I set it to that previously. I want the user to be able to enter the variable.
Thanks.
Oracle has the concept of interactive queries, those that as you said you can run by adding the '&' before your variables names, that is a variable substitution, this concept doesn't exist in MySql, MySql is not interactive and requires the user to enter the values in the variables by using the keyword 'SET' and # (instead of & like in Oracle).
So, no, you cannot do what you are looking for since this is not a client-side implementation either.
BTW, I just noticed this was asked so many years ago, amazing that this is still not added as a feature in mysql.
For a prompt, you must put the char ':' followed by the name of the variable
Example :
select *
from YOUR_TABLE
where YOUR_COLUMN = :your_var
mysql is to run SQL queries .SQL is a query language, it is not for user interaction
See : How to ask MySQL to prompt for values in a query?

Rails database query - is it possible to give a column an alias?

I've got to produce a json feed for an old mobile phone app and some of the labels need to be different from my database column names.
I think the most efficient way of doing this would be to do a create an alias at the database level. So I'm doing things like
Site.where( mobile_visible: true ).select("non_clashing_id AS clientID")
which produces the SQL
SELECT non_clashing_id AS clientID FROM `sites` WHERE `sites`.`mobile_visible` = 1 ORDER BY site_name
If I run this query in MYSQL workbench it produces a column with the heading ClientID as I expect, with the required values.
But if I show the object in a rails view I get {"clientID":null},{"clientID":null},{"clientID":null}
What am I doing wrong? Is there a better way of doing this?
This shows how to access the variable
sites = Site.where( mobile_visible: true ).select("non_clashing_id AS clientID")
sites.each do |site|
puts site.clientID
end
I think by default, activerecord loads column definitions from the database. And, it should load value into existing columns only.
Site.columns
I guess you could add one more item to that array. Or you could use the normal query without alias column name, then add alias_attribute like MurifoX did and overwrite as_json method:
class Site < ActiveRecord::Base
alias_attribute :client_id, :non_clashing_id
def as_json(options={})
options[:methods] = [:client_id]
options[:only] = [:client_id]
super
end
end
Try putting this in your model in addition to the database alias:
class model < ActiveRecord::Base
alias_attribute :non_clashing_id, :client_id
...
end

How to search for multiple strings in a database table column?

I am using Rails 3.2.2 and MySQL. I am searching by user name (for instances, John, Anthony, Mark) a database table column this way:
# User controller
User.search_by_name(params[:search]).order(:name)
# User model
def self.search_by_name(search)
if search
where('users.name LIKE ?', "%#{search}%")
else
scoped
end
end
However, since a name can be composed from two or more strings (for instances, John Henry or Henry John, Anthony Maria or Maria Anthony, Mark Alfred or Alfred Mark), I would like to search users also when in the params[:search] are provided more than one name. I tried to use
def self.search_by_name(search)
if search
search.split(' ').each do |string|
where('users.name LIKE ?', "%#{string}%")
end
else
scoped
end
end
but I get the following error (for instance, given I am searching for John Henry):
NoMethodError (undefined method `order' for ["John", "Henry"]:Array).
How can I properly search for multiple names?
I totally think you should do one of the following,
Mysql full text search:
http://devzone.zend.com/26/using-mysql-full-text-searching/
SphinxSearch:
http://www.ibm.com/developerworks/library/os-php-sphinxsearch/
I recommend sphinxsearch since I use it with various cool features
built in with it.
Support for sphinx is amazing too!
Using the squeel gem.
def self.search_by_name(search)
if search
where(name.like_any search.split)
else
scoped
end
end
Note that this will NOT order by matching level. For that you need a search-engine implementation, like gems for sphinx, solr, xapian
also note that your original use of each is incorrent since you meant to 'OR' the conditions. If you do not mind to issue as many db queries as search terms you could even fake the match level ordering.
def self.search_by_name(search)
if search
results = search.split.map do |string|
where('users.name LIKE ?', "%#{string}%").all
end.flatten
ids = results.map(&:id)
# desc order by count of matches
ordered_results = results.uniq.order { |result| -ids.count(result.id) }
else
scoped.all
end
end
This is not an Arel relation that can be further scoped but a plain array though.
Note the 'all' call, so do not even attempt this on a big db.
Also note that this will not order 'a b' above 'b a' if search is 'a b' etc.
So I'm kinda just having fun.

How to instruct Rails to generate the correct SQL on uniqueness validation when case insensitive

Assume Rails 3 with MySQL DB with Case Insensitive collation
What's the story:
Rails allows you to validate an attribute of a Model with the "uniqueness" validator. BUT the default comparison is CASE SENSITIVE according to Rails documentation.
Which means that on validation it executes SQL like the following:
SELECT 1 FROM `Users` WHERE (`Users`.`email` = BINARY 'FOO#email.com') LIMIT 1
This works completely wrong for me who has a DB with CI Collation. It will consider the 'FOO#email.com' valid, even if there is another user with 'foo#email.com' already in Users table. In other words, this means, that if the user of the application tries to create a new User with email 'FOO#email.com' this would have been completely VALID (by default) for Rails and INSERT will be sent to db. If you do not happen to have unique index on e-mail then you are boomed - row will be inserted without problem. If you happen to have a unique index, then exception will be thrown.
Ok. Rails says: since your DB has case insensitive collation, carry out a case insensitive uniqueness validation.
How is this done? It tells that you can override the default uniqueness comparison sensitivity by setting ":case_sensitive => false" on the particular attribute validator. On validation it creates the following SQL:
SELECT 1 FROM `Users` WHERE (LOWER(`Users`.`email`) = LOWER('FOO#email.com') LIMIT 1
which is a DISASTER on a database table Users that you have designed to have a unique index on the email field, because it DOES NOT USE the index, does full table scan.
I now see that the LOWER functions in SQL are inserted by the UniquenessValidator of ActiveRecord (file uniqueness.rb, module ActiveRecord, module Validations class UniquenessValidator). Here is the piece of code that does this:
if value.nil? || (options[:case_sensitive] || !column.text?)
sql = "#{sql_attribute} #{operator}"
else
sql = "LOWER(#{sql_attribute}) = LOWER(?)"
end
So Question goes to Rails/ActiveRecord and not to MySQL Adapter.
QUESTION: Is there a way to tell Rails to pass the requirement about uniqueness validation case sensitivity to MySQL adapter and not be 'clever' about it to alter the query? OR
QUESTION REPHRASED FOR CLARIFICATION: Is there another way to implement uniqueness validation on an attribute (PLEASE, CAREFUL...I AM NOT TALKING ABOUT e-mail ONLY, e-mail was given as an example) with case sensitivity OFF and with generation of a query that will use a simple unique index on the corresponding column?
These two questions are equivalent. I hope that now, I make myself more clear in order to get more accurate answers.
Validate uniqueness without regard to case
If you want to stick to storing email in upper or lower case then you can use the following to enforce uniqueness regardless of case:
validates_uniqueness_of :email, case_sensitive: false
(Also see this question:
Rails "validates_uniqueness_of" Case Sensitivity)
Remove the issue of case altogether
Rather than doing a case insensitive match, why not downcase the email before validating (and therefore also):
before_validation {self.email = email.downcase}
Since case is irrelevant to email this will simplify everything that you do as well and will head off any future comparisons or database searches you might be doing
I have searched around and the only answer, according to my knowledge today, that can be acceptable is to create a validation method that does the correct query and checks. In other words, stop using :uniqueness => true and do something like the following:
class User
validate :email_uniqueness
protected
def email_uniqueness
entries = User.where('email = ?', email)
if entries.count >= 2 || entries.count == 1 && (new_record? || entries.first.id != self.id )
errors[:email] << _('already taken')
end
end
end
This will definitely use my index on email and works both on create and update (or at least it does up to the point that I have tested that ok).
After asking on the RubyOnRails Core Google group
I have taken the following answer from RubyOnRails Core Google Group: Rails is fixing this problem on 3.2. Read this:
https://github.com/rails/rails/commit/c90e5ce779dbf9bd0ee53b68aee9fde2997be123
Workaround
If you want a case-insensitive comparison do:
SELECT 1 FROM Users WHERE (Users.email LIKE 'FOO#email.com') LIMIT 1;
LIKE without wildcards always works like a case-insensitive =.
= can be either case sensitive or case-insensitive depending on various factors (casting, charset...)
starting with http://guides.rubyonrails.org/active_record_querying.html#finding-by-sql
then adding their input
#Johan,
#PanayotisMatsinopoulos
and this
http://guides.rubyonrails.org/active_record_validations_callbacks.html#custom-methods
and http://www.w3schools.com/sql/sql_like.asp
then we have this:
class User < ActiveRecord::Base
validate :email_uniqueness
protected
def email_uniqueness
like_emails = User.where("email LIKE ?", email))
if (like_emails.count >= 2 || like_emails.count == 1
&& (new_record? || like_emails.first.id != self.id ))
errors[:email] << _('already taken')
end
end
end
validates :email, uniqueness: {case_sensitive: false}
Works like a charm in Rails 4.1.0.rc2
;)
After fighting with MySQL binary modifier, i found a way that removes that modifier from all queries comparing fields (not limited to uniqueness validation, but includes it).
First: Why MySQL adds that binary modifier? That's because by default MySQL compares fields in a case-insensitive way.
Second: Should I care? I always had design my systems to suppose that String comparison are made in a case-insensitive way, so that is a desired feature to me. Be warned if you don't
This is where is added the binary modifier:
https://github.com/rails/rails/blob/ee291b9b41a959e557b7732100d1ec3f27aae4f8/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb#L545
def case_sensitive_modifier(node)
Arel::Nodes::Bin.new(node)
end
So i override this. I create an initializer (at config/initializers) named "mysql-case-sensitive-override.rb" with this code:
# mysql-case-sensitive-override.rb
class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
def case_sensitive_modifier(node)
node
end
end
And that's it. No more binary modifier on my queries :D
Please notice that this does not explain why the "{case_sensitive: false}" option of the validator doesn't works, and does not solves it. It changes the default-and-unoverrideable-case-sensitive behavior for a default-and-unoverrideable-case-insensitive new behavior. I must insist, this also changes for any comparison that actually uses binary modifier for case-sensitive behavior (i hope).