I am new to Ruby on Rails and I have basic knowledge of mysql. I am using MySQL db. My question is -- how to check if a row is exists or not in a table. I have tried this code but it's not going straight to the else block, not the if block:
#tasks = User.find_by("user_name = ? AND password = ?", params[:user_name], params[:password])
if #tasks
redirect_to action: 'index', status: 302
else
redirect_to action: 'detail', status: 302
end
If you want to find if a user with the given name and password exists using Ruby on Rails, then you can do this:
User.where(user_name: params[:user_name], password: params[:password]).exists?
See the RailsGuides: Existence of Objects.
The Cause of the Original Problem?
So this it the code that the original poster originally submitted:
User.find_by("user_name = ? AND password = ?", "#{params[:user_name]}", "#{params[:password]}")
I removed the string interpolation because it was unnecessary
User.find_by("user_name = ? AND password = ?", params[:user_name], params[:password])
and apparently that fixed the problem. I'm not sure why it would make a difference though, the interpolated string should be the same value as the values in the params dictionary.
You can use any of these solutions depending on your requirement.
Sol-1:
User.where(user_name: params[:user_name], password: params[:password]).exists?
Sol-2:
User.find_by_user_name_and_password(params[:user_name], params[:password])
where returns an ActiveRecord::Relation (not an array, even though it behaves much like one), which is a collection of model objects. If nothing matches the conditions, it simply returns an empty relation.
find (and its related dynamic find_by_columnname methods) returns a single model object, or possibly a collection of model objects in an Array (not a Relation). If nothing is found, an ActiveRecord::RecordNotFound exception is raised.
So yes, if you only want and expect a single object, using find is easier, as otherwise you must call Model.where.first.
you can try it as well..
User.find_by_user_name_and_password(params[:user_name], params[:password])
Related
I'm trying to type cast the result from ActiveRecord::Base.connection.select_all, but type info seems to be empty. According to this Stackoverflow Post, the type info can be derived by result.column_types at least when using postgres adapter. But when I run the same code with mysql adapter, I get nothing returned.
result = ActiveRecord::Base.connection.select_all(User.all.to_sql)
result.column_types #=> returns {}
result.cast_value #=> returns uncasted values
I know there's something like this that casts the thing,
User.type_for_attribute('id').deserialize('100')
# => 100
But this won't work well when working with ActiveRecord::Result, because we don't know which column belongs to which ActiveRecord class (at least from the result).
**Question 1. Are there any easy ways to achieve type casting on ActiveRecord::Result?
I followed the source code a bit, and seems like mysql adapter isn't passing the type info when initialising the Result object. Below is how ActiveRecord::Result is initialised (gh code),
module ActiveRecord
class Result
def initialize(columns, rows, column_types = {})
#columns = columns
#rows = rows
#hash_rows = nil
#column_types = column_types
end
...
And here's ActiveRecord's mysql adapter code and you can see it doesn't pass anything as the third argument, whereas, postgres code does.
So I'm guessing type casting for mysql is done somewhere else.
Question 2. Anybody knows where the magic for mysql is happening along with the normal ActiveRecord usage like User.first.id?
I am learning Ruby at the moment and I have written the below code, however it is causing errors when running.
The idea is that a channel will only be inserted in to the database if it is not already present in the database (checked via exists? method).
def exists?(channel)
rs = #con.query("SELECT * FROM channels WHERE name = #{channel}")
return true unless rs.empty?
end
channels.each do |channel|
#con.query("INSERT INTO channels (name, timestamp) VALUES ('#{channel}', '#{Time.now.to_i}')") unless channel.exists?
Here is an error message shown once I include this code:
undefined method `exists?' for "#channel1":String
Is there an error in the code that I've written?
I think you're confused about the syntax. If you want to use your above-defined method, you should have this:
#con.query("INSERT INTO channels (name, timestamp) VALUES ('#{channel}', '#{Time.now.to_i}')") unless exists?(channel)
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).
I have a 'user' table with a field name 'process_salary?' which has a boolean datatype
#user = User.create(params[:user])
if #user.process_salary?
//some code here
else
//some code here
end
When I create a new object of user and check for process_salary it gives me following error
NoMethodError: undefined method `process_salary?' for #<User:0xb6ac2f68>
Why does this error occur? Can I avoid it without changing my column name?
When I check it with the debugger it crashes the first time, but after that it runs properly
The question-mark has a special meaning in ActiveRecord. It can be used to check whether a field is true. You are using it as part of your field name which wasn't such a good idea. You could try if #user.process_salary?? exists but I think ultimately it is easiest to change your database column to be called 'process_salary'.
Side note: The 'rails console' is really helpful for playing around with models.
As cellcortex posted, question marks at the end of column names are tricky in Rails. If you need to have it there for legacy reasons, you might be able access the attribute as follows:
#user['process_salary?']
or the more verbose:
#user.read_attribute['process_salary?']
You can of course test for nil using .nil?.
Currently my login controller doesn't work because i can't seem to fetch the username and password.
I'm currently using something like this:
form_username = str(request.params.get('username'))
db_user = meta.Session.query(User).filter_by(username=form_username)
if db_user is None:
return redirect('auth/error')
No matter which username is use, db_user always returns True and thus never goes to auth/error. I used the shell to play with this and i was able establish a connection with the database, so i'm not sure what i'm doing wrong here.
You're on the right track; you just need to go one step further.
As you mention in the comments above, filter_by() returns a Query object.
You need to specify which method of the Query object you want to use to actually run the query and return some results.
In this case, I'd recommend first().
db_user = meta.Session.query(User).filter_by(username=form_username).first()
Documentation (my emphasis):
Return the first result of this Query or None if the result doesn’t contain any row. This results in an execution of the underlying query.