How to make find_or_initialize case insensitive on a Postgres DB? - ruby-on-rails

In a Rails 3.2 app I have a Post model that belongs to a Category. In the new post form is a text field for assigning a category or creating a new category.
It works via the following method on the Post model
def category_name=(name)
if name.present?
post_cat = category.find_or_initialize_by_name(name)
if post_cat.new_record?
post_cat.save(:validate => false)
self.category = post_cat
else
self.category = post_cat
end
end
end
This is more or less working, but is case sensitive. For example, the database now contains records for "Featured", "featured" and "FEATURED" categories.
How can I make the above find_or_initialize function behave as though it were case insensitive. I am using a Postgres database, and I have a suspicion that this is not quite as easy as "making find_or_initialize case insensitive".
I'm grateful for any advice as to best practice, what to consider, and useful references.

I refer to your comment above: Maybe you could add a second field, containing the value of the string which is displayed to the user, and find_or_initialize by the name column:
post_cat = category.find_or_initialize_by_name(name.downcase)
post_cat.display_name = name if post_cat.new_record?
So the second line ensures, that an existing record doesn't get overriden by another user, who tries to add categories like bRandnAmE. The only pitfall I see is, that the first user, who creates the category, has to spell it correct.

Related

Rails How do I find case insensitive values that are not already associated to the user?

Newbie Rails developer here so please bare with me.
I have a table called Ingredients where it contains a title field and an association to a User. A user can have many ingredients.
I want to query the database to get the ingredients that are not already available to a User.
I tried doing something like this with Rails:
#ingredients = current_user.ingredients
#community_ingredients = Ingredient.all.excluding(#ingredients).pluck(:title, :id)
But the problem is that this still returns values that are the same & only the case is different.
How can I achieve this outcome?
Try following queries.
#community_ingredients = Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(users: { id: nil } ).pluck(:title, :id)
OR
Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(ingredients: {user_id: nil } ).pluck(:title, :id)
OR
Ingredient.includes(:user).where("users.user_id = ?", current_user.id).where(users: { ingredient_id: nil } ).pluck(:title, :id)
Choose right query based on your association and feel free to suggest me so I can remove the extra one.
Most probably the first or second query will work, I strongly feel the third might not be the case.
Let's say this one is not working for you and you want to have solution based on your architecture.
#ingredients = current_user.ingredients.pluck(:title)
#community_ingredients = Ingredient.where.not("lower(title) IN (?)", #ingredients.map(&:downcase)).pluck(:title, :id)
So basically we need to convert both column value and the matching list in same case.
So we have converted to downcase.
here is how it looks in my local system, just make sure it's working that way.

How to validate uniqueness of a field without saving in Rails?

Suppose you have User has_many Books. Each book has a name field.
The user enters their books and it is submitted to the app as an array of names. The array of names will replace any existing books.
If the update fails, then the books should not be changed.
class Book
belongs_to :user
validates_uniquness_of :name, scope: [:user]
How to check the validity of each book without saving?
For example:
['Rails Guide', 'Javascript for Dummies'] would be valid.
['Javascript for Dummies', 'Javascript for Dummies'] would not be valid.
params[:books].each{| b | Book.new(b).valid? } will not work because the book has to be saves to get the uniqueness.
Mongoid
You can use an Active Record Transaction. Start the transaction, call save, and if it fails then the entire transaction will be rolled back. For example:
Book.transaction do
params[:books].each{ |b| Book.new(b).save! }
end
The entire transaction is aborted if there is an exception. You should handle this case by catching ActiveRecord::RecordInvalid.
You can use Array#map to convert array of books attributes to array of books names. Then use Array#uniq to remove duplicates from array of books names, and then check if resulting array has the same size as the original array of books attributes:
are_books_uniq = params[:books].map{|b| b[:name]}.uniq.size == params[:books].size
This way you can perform your check, without touching the database. But to be on a safe side, you should save all the books inside a transaction (see #Aaron's answer).
This turned out to be much more complicated than I imagined.
The solution I came up with looks like:
def update params
names = params.delete( :books )
new_books = names.map{| title | Book.new( name:name )}
validate_books_for new_books
return false if errors.present?
return false unless super( params )
self.books = new_books
self
end
Most of the complexity comes from the coupling of the 2 models. I can see why it is not a good idea to couple models. Perhaps a better design would be to store the books as an array.

Rails 4: Ordering Users based on the *last word* in their `name` attribute?

I have a User model with a name attribute. In my PagesController, I want to set an instance variable equal to all of the User objects, but I want to order them based on last name. I know I can do:
#sortedusers = User.order(:name => :asc)
to order the users based on their name attribute, but how do I do it based on last name? (i.e. how do I order the Users based on the last word of their name attributes?
Thanks.
Define a virtual attribute last name in your model
def last_name
name.split(' ').last
end
and then
User.order(:last_name => :asc)
I'd suggest storing the users' last name separately. Barring that, you can use sort_by, like so:
items.sort_by! { |a| a.split(' ') } or something along those lines. Note, this most likely cannot be used straight, it's merely intended to point you in the right direction, as most of my answers are.

Rails checking if a record exists in database

What is the most efficient of way of checking if a database will return a record before processing it. Example: Truck.where("id = ?", id).select('truck_no').first.truck_no
This may or may not return a truck if the truck exists. What is the most efficient way for me to ensure the page will not crash when processing this request. How would I handle this both in the view and the controller if lets say I was using a loop to go through each truck and print out its number.
If the record does not exist I would like to be able to print out a message instead saying no records found.
If you want to check for the existence of an object why not use exists?
if Truck.exists?(10)
# your truck exists in the database
else
# the truck doesn't exist
end
The exists? method has the advantage that is not selecting the record from the database (meaning is faster than selecting the record).
The query looks like:
SELECT 1 FROM trucks where trucks.id = 10
You can find more examples in the Rails documentation for #exists?.
Here is how you can check this.
if Trucks.where(:id => current_truck.id).blank?
# no truck record for this id
else
# at least 1 record for this truck
end
where method returns an ActiveRecord::Relation object (acts like an array which contains the results of the where), it can be empty but never be nil.
OP actual use case solution
The simplest solution is to combine your DB check and retrieval of data into 1 DB query instead of having separate DB calls. Your sample code is close and conveys your intent, but it's a little off in your actual syntax.
If you simple do Truck.where("id = ?", id).select('truck_no').first.truck_no and this record does NOT exists, it will throw a nil error when you call truck_no because first may retrieve a nil record if none are found that match your criteria.
That's because your query will return an array of objects that match your criteria, then you do a first on that array which (if no matching records are found) is nil.
A fairly clean solution:
# Note: using Rails 4 / Ruby 2 syntax
first_truck = Truck.select(:truck_no).find_by(id) # => <Truck id: nil, truck_no: "123"> OR nil if no record matches criteria
if first_truck
truck_number = first_truck.truck_no
# do some processing...
else
# record does not exist with that criteria
end
I recommend using clean syntax that "comments" itself so others know exactly what you're trying to do.
If you really want to go the extra mile, you could add a method to your Truck class that does this for you and conveys your intent:
# truck.rb model
class Truck < ActiveRecord::Base
def self.truck_number_if_exists(record_id)
record = Truck.select(:truck_no).find_by(record_id)
if record
record.truck_no
else
nil # explicit nil so other developers know exactly what's going on
end
end
end
Then you would call it like so:
if truck_number = Truck.truck_number_if_exists(id)
# do processing because record exists and you have the value
else
# no matching criteria
end
The ActiveRecord.find_by method will retrieve the first record that matches your criteria or else returns nil if no record is found with that criteria. Note that the order of the find_by and where methods is important; you must call the select on the Truck model. This is because when you call the where method you're actually returning an ActiveRelation object which is not what you're looking for here.
See ActiveRecord API for 'find_by' method
General solutions using 'exists?' method
As some of the other contributors have already mentioned, the exists? method is engineered specifically to check for the existence of something. It doesn't return the value, just confirms that the DB has a record that matches some criteria.
It is useful if you need to verify uniqueness or accuracy of some piece of data. The nice part is that it allows you to use the ActiveRelation(Record?) where(...) criteria.
For instance, if you have a User model with an email attribute and you need to check if an email already exists in the dB:
User.exists?(email: "test#test.com")
The benefit of using exists? is that the SQL query run is
SELECT 1 AS one FROM "users" WHERE "users"."email" = 'test#test.com' LIMIT 1
which is more efficient than actually returning data.
If you need to actually conditionally retrieve data from the DB this isn't the method to use. However, it works great for simple checking and the syntax is very clear so other developers know exactly what you're doing. Using appropriate syntax is critical in projects with multiple developers. Write clean code and let the code "comment" itself.
If you just want to check whether the record exists or not. Go with the #cristian's answer i.e.
Truck.exists?(truck_id) # returns true or false
But if truck exists and you want to access that truck then you will have to find truck again which will lead to two database queries. If this is the case go with
#truck = Truck.find_by(id: truck_id) #returns nil or truck
#truck.nil? #returns true if no truck in db
#truck.present? #returns true if no truck in db
You could just do:
#truck_no = Truck.where("id = ?", id).pluck(:truck_no).first
This will return nil if no record is found, or truck_no of only the first record otherwise.
Then in your view you could just do something like:
<%= #truck_no || "There are no truck numbers" %>
If you want to fetch and display multiple results, then in your controller:
#truck_nos = Truck.where("id = ?", id).pluck(:truck_no)
and in your view:
<% truck_nos.each do |truck_no| %>
<%= truck_no %>
<% end %>
<%= "No truck numbers to iterate" if truck_nos.blank? %>
Rails has a persisted? method
for using like you want

Postgres ORDER BY values in IN list using Rails Active Record

I receive a list of UserIds(about 1000 at a time) sorted by 'Income'. I have User records in "my system's database" but the 'Income' column is not there. I want to retrieve the Users from "my system's database"
in the Sorted Order as received in the list. I tried doing the following using Active Record expecting that the records would be retrieved in the same order as in the Sorted List but it does not work.
//PSEUDO CODE
User.all(:conditions => {:id => [SORTED LIST]})
I found an answer to a similar question at the link below, but am not sure how to implement the suggested solution using Active Record.
ORDER BY the IN value list
Is there any other way to do it?
Please guide.
Shardul.
Your linked to answer provides exactly what you need, you just need to code it in Ruby in a flexible manner.
Something like this:
class User
def self.find_as_sorted(ids)
values = []
ids.each_with_index do |id, index|
values << "(#{id}, #{index + 1})"
end
relation = self.joins("JOIN (VALUES #{values.join(",")}) as x (id, ordering) ON #{table_name}.id = x.id")
relation = relation.order('x.ordering')
relation
end
end
In fact you could easily put that in a module and mixin it into any ActiveRecord classes that need it, since it uses table_name and self its not implemented with any specific class names.
MySQL users can do this via the FIELD function but Postgres lacks it. However this questions has work arounds: Simulating MySQL's ORDER BY FIELD() in Postgresql

Resources