Find by multiple conditions in rails - ruby-on-rails

I want to search a table with multiple conditions in Rails. I am using Active record and rails version 3.1.0.
I have Movies object, and want to achieve the equivalent of the following in rails:
Select * from Movies where rating = 'R' OR rating = 'PG'
I tried the following but it does not work
#filtered = Movies.find(:all, :conditions => { :rating => 'R', :rating => 'PG' })
Can you please provide help to write an equivalent of SQL query mentioned above.

One way would be to build an "IN" condition with:
#filtered = Movie.where(:rating => ['R', 'PG']).all
EDIT: I changed your class to "Movie" from "Movies". I assume that's what you will want.

In Rail 4, find with multiple conditions for example consider find Profile with first_name and last_name
Profile.find_by first_name: 'stack', last_name: 'flow'
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil
Profile.find_by! first_name: 'stack', last_name: 'flow'
Like find_by, except that if no record is found, raises an ActiveRecord::RecordNotFound error.
For more information read Rails Finder Method
1: http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find_byIn Rail 4, find with multiple conditions for example consider find Profile with first_name and last_name
Profile.find_by first_name: 'stack', last_name: 'flow'
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil
Profile.find_by! first_name: 'stack', last_name: 'flow'
Like find_by, except that if no record is found, raises an ActiveRecord::RecordNotFound error.
For more information read Rails Finder Method

i guess that would be
Movie.where("rating = ? OR rating = ?", 'R', 'PG')
have a look at the guides for more info: http://guides.rubyonrails.org/active_record_querying.html#conditions
i would recommend using an IN statement instead.

You can do it using:
Movie.where(:rating => ['R','PG'])

Related

Location based search in Rails using geokit problem

I have a rather complex (or so it seems) geokit based location search on my site. In short, I have a model named "Campaigns" that belongs to another model called "Businesses". When I am searching on the site, users are searching for "Campaigns", but I want all appropriate models to show up in the results if they search for that Campaigns business name. For this I am doing a joins in the search.
I additionally have the geokit plugin and gem installed so that users can get results for these searches only within a set distance from the origin location (which they provide). However, I am getting strange results when I add this location functionality into the site.
If I use the following search (simplified for brevity, but tested in the console):
Campaign.find(:all,
:joins => :business,
:conditions => ['businesses.name LIKE ?', "%Koo Koo Roo%"]
)
I get the appropriate result, which is:
[#<Campaign id: 12, user_id: 4, business_id: 8, created_at: "2011-01-14 16:22:31", updated_at: "2011-01-14 16:25:20", lat: #<BigDecimal:2ad295a9eda8,'0.34154891E2',18(18)>, lng: #<BigDecimal:2ad295a9ed08,'-0.118358834E3',18(18)>>]
But if I try to add geokit based location search parameters onto this search, like so:
Campaign.find(:all,
:joins => :business,
:origin => "90066",
:within => 25,
:conditions => ['businesses.name LIKE ?', "%Koo Koo Roo%"]
)
I get the following result:
[#<Campaign id: 8, user_id: 4, business_id: 8, created_at: "2011-01-14 16:25:20", updated_at: "2011-01-14 16:25:20", lat: #<BigDecimal:2ad29933e618,'0.34154891E2',18(18)>, lng: #<BigDecimal:2ad29933e578,'-0.118358834E3',18(18)>>]
Which is almost identical. The only difference is, for the second result, it seems to be passing the business_id as the Campaign id. I have verified this twice, and both times it is the same thing, the campaign id gets replaced with the business_id.
I should mention, that this is true no matter what :within distance i enter.
What am I doing wrong here? Am I missing something? I can't seem to figure it out, it all looks sound to me, but apparently not! I don't understand how the results could be screwed up like this by geokit.
in my models I simply have:
Business.rb
has_many :campaigns
Campaign.rb
belongs_to :business
Any help would be appreciated. Am I missing some sort of association? I don't have geokit caching turned on.
Thanks!
It's because the "id" field is being overwritten by dodgy join code. When you join, you've got two "id" fields (one for business and one for campaign). without an explicit instruction as to which is "the" id, the DB guesses.
Unfortunately, the one of the versions of rails has a bug where it did not explicitly state that the main Active Record id (in this case Campaign.id) was the id that counts... and the db was guessing the wrong one, overwriting id with the Business.id.
You've already discovered the easy (but more hacky) fix... the other is upgrading rails.

About Query methods Conditions in Rails 3

Hash way works just fine:
drummers = Drummer.where(:gender => true)
=> [#<Drummer id: 1, first_name: "Bernard", middle_name: nil, second_name: "Purdie", nick_name: "Pretty Purdie", gender: true, created_at: "2010-12-05 02:47:56", updated_at: "2010-12-05 02:50:42">]
But the same thing in String way:
drummers = Drummer.where("gender = true")
I got below error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: TRUE: SELECT "drummers".* FROM "drummers" WHERE (gender = TRUE)
anybody could tell mewhy?
This is a sqlite error, not rails. When you say gender = true, it's looking for a column that is named true. Sqlite does not have booleans, so the correct way to do this is Drummer.where("gender = 1").
You should avoid using strings where possible in your Arel queries.
If you're joining a table that also contains a field named 'gender' then this will break, because it's ambiguous. Using where(:gender => true) will automatically be namespaced for you, so that won't happen.
As your example shows, using strings also can create portability problems, if you're using a feature that your backend doesn't support.
I've been using a plugin called MetaWhere to augment ARel's syntax, making strings less necessary.

Get ID of Rails Model before saving...?

How do you get the id of a rails model before it is saved?
For example, if I create a new model instance, how can I get its ID before it is saved?
I know that the id is created onsave and according to the database but is there a workaround?
I was looking for this too, and I found an answer:
Let's suppose model name is "Model" and table name is "models"
model.rb
before_save {
next_id=Model.connection.select_value("Select nextval('models_id_seq')")
}
This will output the value your record will take for id IF it gets saved
Usually when people think they need to do this they actually do not need to do it. Like John says, explain what you are trying to do and someone can probably suggest a way to do it without having to know the id in advance.
This is less a Rails question and more a database question. This is a problem that will present itself in any web application framework, and the solution is the same in all places. You have to use a database transaction.
The basic flow will work like this.
Open a transaction
Save your model
Use the ID assigned by the database
If it turns out you actually don't want to keep this model in the database, roll back the transaction.
If it turns out you want to keep the model in the database, commit the transaction.
The main thing you will notice from this approach is that there will be gaps in your IDs where you rolled back the transaction.
Using the default Rails convention of an auto-incrementing integer primary key, there's no way to get the ID of a model before it's saved because it's generated by the RDBMS when the new row is inserted in the relevant table.
What problem are you actually trying to solve?
Most of the time when I needed an id can be grouped into a short list.
When creating nested associations or connectin of the associations through.
Let's assume we have: :user that have :pets through :user_pets association, where we will save their type.
If we have a properly configured "has_many: through Association" we can just
User.pets.create(name: "Rex") but this is too simplistic, as we want to creat :pet type in :user_pets.
u = User.create(name: "Cesar")
u.id # => 1 # works fine
p = u.pets.create(name: 'Rex')
# => rails will create UserPets => {id: 1, user_id: 1, pet_id: 1} for us
# But now we have a problem, how do we find :user_pets of our :pet?
# remember we still need to update the :type, the ugly (wrong) way:
up = p.user_pets.first
up.type = 'dog'
up.save! # working but wrong
# Do you see the problems here? We could use an id
P = Pet.new( name: "Destroyer" )
p.id # will not work, as the pet not saved yet to receive an id
up = UserPet.new( user_id: U.id, pet_id: p.id )
# => UserPet {id: 2, user_id: 1, pet_id: nil} # as we expected there is no id.
# What solutions do we have? Use nested creation!
# Good
up = UserPet.new(user_id: u.id, type: "dog")
# even better
up = u.user_pets.new(type: "dog")
# it's just a shortcut for the code above,
# it will add :user_id for us, so let's just remember it.
# Now lets add another 'new' from our creatd 'user_pet'
p = up.pets.new(name: "Millan")
user.save!
# => UserPet: {id: 3, user_id: 1, pet_id: 2, type: 'dog'} # => Pet: {id: 2, name: "Sam"}
# everything is working! YEY!
# we can even better, than writing in the beginning "User.create",
# we can write "User.new" and save it with all the nested elements.
You saw how this created all the ids for us? Let's move to something even more complex!
Now we have an additional table :shampoo that exactly as :user_pet, belongs to a :pet and a :user
We need to create it without knowing the id of the :user and :pet
u = User.new('Mike')
up = u.user_pets.new(type: "cat")
p = up.pets.new(name: "Rowe")
# But what are we doing now?
# If we do:
s = u.shampoos.new(name: "Dirty Job")
# => Shampoo: {user_id: 2, pet_id: nil, name: "..."}
# We get "pet_id: nil", not what we want.
# Or if we do:
s = p.shampoos.new(name: "Schneewittchen")
# => Shampoo: {user_id: nil, pet_id: 3, name: "..."}
# We get "user_id: nil", in both cases we do not get what we want.
# So we need to get the id of not created record, again.
# For this we can just do as in the first example (order is not important)
s = u.shampoos.new(name: "Mission Impossible")
# => Shampoo: {id: 3, user_id: 2, pet_id: nil, name: "..."}
s.pet = p # this will give the missing id, to the shampoo on save.
# Finish with save of the object:
u.save! #=> Shampoo: {id: 3, user_id: 2, pet_id: 3, name: '...'} # => Pet: ...
# Done!
I tried to cover most common causes when you need element id, and how to overcome this. I hope it will be useful.
I don't believe there are any workarounds since the id is actually generated by the database itself. The id should not be available until after the object has been saved to the database.
Consider doing what you want right after the instance is created.
after_create do
print self.id
end
First understand the structure of database.
Id is generated using sequence
increment done by 1 (specified while creating sequence)
Last entry to database will have highest value of id
If you wanted to get id of record which is going to be saved,
Then you can use following:
1. id = Model.last.id + 1
model = Model.new(id: id)
model.save
# But if data can be delete from that dataabse this will not work correctly.
2. id = Model.connection.select_value("Select nextval('models_id_seq')")
model = Model.new(id: id)
model.save
# Here in this case if you did not specified 'id' while creating new object, record will saved with next id.
e.g.
id
=> 2234
model = Model.new(id: id)
model.save
# Record will be created using 'id' as 2234
model = Model.new()
model.save
# Record will be created using next value of 'id' as 2235
Hope this will help you.
I just ran into a similar situation when creating a data importer. I was creating a bunch of records of different types and associating them before saving. When saving, some of the records threw validation errors because they had validate_presence_of a record that was not yet saved.
If you are using postgres, active record increments the id it assigns to a Model by keeping a sequence named models_id_seq (sales_id_seq for Sale etc.) in the database. You can get the next id in this sequence and increment it with the following function.
def next_model_id
ActiveRecord::Base.connection.execute("SELECT NEXTVAL('models_id_seq')").first["nextval"].to_i
end
However, this solution is not good practice as there is no guarantee that active record will keep id sequences in this way in the future. I would only use this if it was used only once in my project, saved me a lot of work and was well documented in terms of why it should not be used frequently.
I know it's an old question, but might as well throw my answer in case anyone needs to reference it
UserModel
class User < ActiveRecord::Base
before_create :set_default_value
def set_default_value
self.value ||= "#{User.last.id+1}"
end

Case-insensitive search in Rails model

My product model contains some items
Product.first
=> #<Product id: 10, name: "Blue jeans" >
I'm now importing some product parameters from another dataset, but there are inconsistencies in the spelling of the names. For instance, in the other dataset, Blue jeans could be spelled Blue Jeans.
I wanted to Product.find_or_create_by_name("Blue Jeans"), but this will create a new product, almost identical to the first. What are my options if I want to find and compare the lowercased name.
Performance issues is not really important here: There are only 100-200 products, and I want to run this as a migration that imports the data.
Any ideas?
You'll probably have to be more verbose here
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
This is a complete setup in Rails, for my own reference. I'm happy if it helps you too.
the query:
Product.where("lower(name) = ?", name.downcase).first
the validator:
validates :name, presence: true, uniqueness: {case_sensitive: false}
the index (answer from Case-insensitive unique index in Rails/ActiveRecord?):
execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"
I wish there was a more beautiful way to do the first and the last, but then again, Rails and ActiveRecord is open source, we shouldn't complain - we can implement it ourselves and send pull request.
If you are using Postegres and Rails 4+, then you have the option of using column type CITEXT, which will allow case insensitive queries without having to write out the query logic.
The migration:
def change
enable_extension :citext
change_column :products, :name, :citext
add_index :products, :name, unique: true # If you want to index the product names
end
And to test it out you should expect the following:
Product.create! name: 'jOgGers'
=> #<Product id: 1, name: "jOgGers">
Product.find_by(name: 'joggers')
=> #<Product id: 1, name: "jOgGers">
Product.find_by(name: 'JOGGERS')
=> #<Product id: 1, name: "jOgGers">
You might want to use the following:
validates_uniqueness_of :name, :case_sensitive => false
Please note that by default the setting is :case_sensitive => false, so you don't even need to write this option if you haven't changed other ways.
Find more at:
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
Several comments refer to Arel, without providing an example.
Here is an Arel example of a case-insensitive search:
Product.where(Product.arel_table[:name].matches('Blue Jeans'))
The advantage of this type of solution is that it is database-agnostic - it will use the correct SQL commands for your current adapter (matches will use ILIKE for Postgres, and LIKE for everything else).
In postgres:
user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
Quoting from the SQLite documentation:
Any other character matches itself or
its lower/upper case equivalent (i.e.
case-insensitive matching)
...which I didn't know.But it works:
sqlite> create table products (name string);
sqlite> insert into products values ("Blue jeans");
sqlite> select * from products where name = 'Blue Jeans';
sqlite> select * from products where name like 'Blue Jeans';
Blue jeans
So you could do something like this:
name = 'Blue jeans'
if prod = Product.find(:conditions => ['name LIKE ?', name])
# update product or whatever
else
prod = Product.create(:name => name)
end
Not #find_or_create, I know, and it may not be very cross-database friendly, but worth looking at?
Similar to Andrews which is #1:
Something that worked for me is:
name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
This eliminates the need to do a #where and #first in the same query. Hope this helps!
Another approach that no one has mentioned is to add case insensitive finders into ActiveRecord::Base. Details can be found here. The advantage of this approach is that you don't have to modify every model, and you don't have to add the lower() clause to all your case insensitive queries, you just use a different finder method instead.
Upper and lower case letters differ only by a single bit. The most efficient way to search them is to ignore this bit, not to convert lower or upper, etc. See keywords COLLATION for MSSQL, see NLS_SORT=BINARY_CI if using Oracle, etc.
Find_or_create is now deprecated, you should use an AR Relation instead plus first_or_create, like so:
TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)
This will return the first matched object, or create one for you if none exists.
An alternative can be
c = Product.find_by("LOWER(name)= ?", name.downcase)
Case-insensitive searching comes built-in with Rails. It accounts for differences in database implementations. Use either the built-in Arel library, or a gem like Squeel.
There are lots of great answers here, particularly #oma's. But one other thing you could try is to use custom column serialization. If you don't mind everything being stored lowercase in your db then you could create:
# lib/serializers/downcasing_string_serializer.rb
module Serializers
class DowncasingStringSerializer
def self.load(value)
value
end
def self.dump(value)
value.downcase
end
end
end
Then in your model:
# app/models/my_model.rb
serialize :name, Serializers::DowncasingStringSerializer
validates_uniqueness_of :name, :case_sensitive => false
The benefit of this approach is that you can still use all the regular finders (including find_or_create_by) without using custom scopes, functions, or having lower(name) = ? in your queries.
The downside is that you lose casing information in the database.
You can also use scopes like this below and put them in a concern and include in models you may need them:
scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }
Then use like this:
Model.ci_find('column', 'value')
If you're using postgres (probably others), I like this solution.
Product.find_by("name ilike 'bLue JEaNS'")
I like this better for a couple reasons.
Clearer connection to database action -> you can just copy paste that into where ...
If you choose to add a wildard %, it's straightforward.
Assuming that you use mysql, you could use fields that are not case sensitive: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
user = Product.where(email: /^#{email}$/i).first
Some people show using LIKE or ILIKE, but those allow regex searches. Also you don't need to downcase in Ruby. You can let the database do it for you. I think it may be faster. Also first_or_create can be used after where.
# app/models/product.rb
class Product < ActiveRecord::Base
# case insensitive name
def self.ci_name(text)
where("lower(name) = lower(?)", text)
end
end
# first_or_create can be used after a where clause
Product.ci_name("Blue Jeans").first_or_create
# Product Load (1.2ms) SELECT "products".* FROM "products" WHERE (lower(name) = lower('Blue Jeans')) ORDER BY "products"."id" ASC LIMIT 1
# => #<Product id: 1, name: "Blue jeans", created_at: "2016-03-27 01:41:45", updated_at: "2016-03-27 01:41:45">
You can use like this in model
scope :matching, lambda { |search, *cols|
where cols.flatten.map{|col| User.arel_table[col].matches("%#{search}%") }.inject(:or)
}
and use wherever you like this
User.matching(params[:search], :mobile_number, :name, :email)
You can pass multiple column for search
for single column search you can use like this
User.where(User.arel_table[:column].matches("%#{search}%"))
So far, I made a solution using Ruby. Place this inside the Product model:
#return first of matching products (id only to minimize memory consumption)
def self.custom_find_by_name(product_name)
##product_names ||= Product.all(:select=>'id, name')
##product_names.select{|p| p.name.downcase == product_name.downcase}.first
end
#remember a way to flush finder cache in case you run this from console
def self.flush_custom_finder_cache!
##product_names = nil
end
This will give me the first product where names match. Or nil.
>> Product.create(:name => "Blue jeans")
=> #<Product id: 303, name: "Blue jeans">
>> Product.custom_find_by_name("Blue Jeans")
=> nil
>> Product.flush_custom_finder_cache!
=> nil
>> Product.custom_find_by_name("Blue Jeans")
=> #<Product id: 303, name: "Blue jeans">
>>
>> #SUCCESS! I found you :)

Getting types of the attributes in an ActiveRecord object

I would like to know if it is possible to get the types (as known by AR - eg in the migration script and database) programmatically (I know the data exists in there somewhere).
For example, I can deal with all the attribute names:
ar.attribute_names.each { |name| puts name }
.attributes just returns a mapping of the names to their current values (eg no type info if the field isn't set).
Some places I have seen it with the type information:
in script/console, type the name of an AR entity:
>> Driver
=> Driver(id: integer, name: string, created_at: datetime, updated_at: datetime)
So clearly it knows the types. Also, there is .column_for_attribute, which takes an attr name and returns a column object - which has the type buried in the underlying database column object, but it doesn't appear to be a clean way to get it.
I would also be interested in if there is a way that is friendly for the new "ActiveModel" that is coming (rails3) and is decoupled from database specifics (but perhaps type info will not be part of it, I can't seem to find out if it is).
Thanks.
In Rails 3, for your model "Driver", you want Driver.columns_hash.
Driver.columns_hash["name"].type #returns :string
If you want to iterate through them, you'd do something like this:
Driver.columns_hash.each {|k,v| puts "#{k} => #{v.type}"}
which will output the following:
id => integer
name => string
created_at => datetime
updated_at => datetime
In Rails 5, you can do this independently of the Database. That's important if you use the new Attributes API to define (additional) attributes.
Getting all attributes from a model class:
pry> User.attribute_names
=> ["id",
"firstname",
"lastname",
"created_at",
"updated_at",
"email",...
Getting the type:
pry> User.type_for_attribute('email')
=> #<ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString:0x007ffbab107698
#limit=255,
#precision=nil,
#scale=nil>
That's sometimes more information than needed. There's a convenience function that maps all these types down to a core set (:integer, :string etc.)
> User.type_for_attribute('email').type
=> :string
You can also get all that data in one call with attribute_types which returns a 'name': type hash.
You can access the types of the columns by doing this:
#script/console
Driver.columns.each {|c| puts c.type}
If you want to get a list of all column types in a particular Model, you could do:
Driver.columns.map(&:type) #gets them all
Driver.columns.map(&:type).uniq #gets the unique ones
In rails 5 this will give you a list of all field names along with their data type:
Model_Name.attribute_names.each do |k| puts "#{k} = #{Model_Name.type_for_attribute(k).type}" end
Rails 5+ (works with virtual attributes as well):
Model.attribute_types['some_attribute'].type
This snippet will give you all the attributes of a model with the associated database data types in a hash. Just replace Post with your Active Record Model.
Post.attribute_names.map {|n| [n.to_sym,Post.type_for_attribute(n).type]}.to_h
Will return a hash like this.
=> {:id=>:integer, :title=>:string, :body=>:text, :created_at=>:datetime, :updated_at=>:datetime, :topic_id=>:integer, :user_id=>:integer}
Assuming Foobar is your Active Record model. You can also do:
attributes = Foobar.attribute_names.each_with_object({}) do |attribute_name, hash|
hash[attribute_name.to_sym] = Foobar.type_for_attribute(attribute_name).type
end
Works on Rails 4 too
In Rails 4 You would use Model.column_types.

Resources