Rails: better way to check for associated object by name - ruby-on-rails

I have the following method in my Product model:
def has_feature? (feature_name)
self.features.where(:name=>feature_name).present?
end
This seems to be very slow. Is there a better way of checking to see if my product has a feature with a given name?

.present? isn't a standard method on an ActiveRecord::Relation which is what where returns. Calling .present? is fetching all of the features with the given name, changing into an array and then calling .present? on the array.
If you call .exists? instead then the database query that is performed is slightly more optimised to checking whether that feature exists or not.
However, I'm not sure you'll notice much of a performance differences between the two options. The most likely cause is that you're missing a database index. Assuming that your association is Product has_many :features the database query that will get generated would be improved by an index on both features.product_id and features.name.

One other way of writing this is using scope in your Feature model and see if it helps and do a present? on the resulting output of that scope
scope :includes_feature, lambda{ |feature|
{ :name => feature }
}
so finally what you can do is product.features.includes_feature(feature_name)

Related

Sortable UUIDs and overriding ActiveRecord::Base

I'm wanting to use UUIDs in an app I'm building and am running into a bit of a problem. Due to UUIDs (v4) not being sortable because they're randomly generated, I'm trying to override ActiveRecord::Base#first, but Rails isn't too pleased with that. It yells at me saying ArgumentError: You tried to define a scope named "first" on the model "Item", but Active Record already defined a class method with the same name. Do I have to use a different method if I want to sort and have it sort correctly?
Here's the sauce:
# lib/sortable_uuid.rb
module SortableUUID
def self.included(base)
base.class_eval do
scope :first, -> { order("created_at").first }
scope :last, -> { order("created_at DESC").first }
end
end
end
# app/models/item.rb
class Item < ActiveRecord::Base
include SortableUUID
end
Rails 4.2, Ruby 2.2.2
Reference:
http://blog.nakonieczny.it/posts/rails-support-for-uuid/
http://linhmtran168.github.io/blog/2014/03/17/postgres-uuid-in-rails/ ( Drawbacks section )
Rails 6 (currently in version 6.0.0rc1) comes to rescue with implicit_order_column!
To order by created_at and make .first, .last, .second etc. respect it is as simple as:
class ApplicationRecord < ActiveRecord::Base
self.implicit_order_column = :created_at
end
First of all, first and last aren't as simple as you seem to think they are: you're completely neglecting the limit argument that both of those methods support.
Secondly, scope is little more than a fancy way of adding class methods that are intended to return queries. Your scopes are abusing scope because they return single model instances rather than queries. You don't want to use scope at all, you're just trying to replace the first and last class methods so why don't you just override them? You'd need to override them properly though and that will require reading and understanding the Rails source so that you properly mimic what find_nth_with_limit does. You'd want to override second, third, ... and the rest of those silly methods while you're at it.
If you don't feel right about replace first and last (a good thing IMO), then you could add a default scope to order things as desired:
default_scope -> { order(:created_at) }
Of course, default scopes come with their own set of problems and sneaking things into the ORDER BY like this will probably force you into calling reorder any time you actually want to specify the ORDER BY; remember that multiple calls to order add new ordering conditions, they don't replace one that's already there.
Alternatively, if you're using Rails6+, you can use Markus's implicit_order_column solution to avoid all the problems that default scopes can cause.
I think you're going about this all wrong. Any time I see M.first I assume that something has been forgotten. Ordering things by id is pretty much useless so you should always manually specify the order you want before using methods like first and last.
After replacing id with uuid, I experienced some weirdness in the way associations were allocating foreign keys, and it wasn't that .last and .first, but instead because I simply forgot to add default: 'gen_random_uuid()' to one of the tables using a uuid. Once I fixed that, the problem was solved.
create_table :appointments, id: :uuid, default: 'gen_random_uuid()' do |t|

In Rails, can I order a query by a delegate method?

I'm having difficulty ordering a query by a delegate method. I've been tasked with helping upgrade a fairly large Rails 3 application to Rails 4. I've come across this query in an index action. I am aware the naming of these objects is horrible and confusing.
# measurements_controller.rb
def index
#measurements = Measurement.includes(:identifier).order(:name)
end
In Rails 4, I'm getting this error:
ERROR: column info_items.name does not exist LINE 1: ...D (info_item_identifiers.name LIKE '%') ORDER BY "info_item...
So I took a look at the models and found:
# measurement.rb
class Measurement < InfoItem
...
end
# info_item.rb
belongs_to :identifier, class_name: 'InfoItemIdentifier'
delegate :name, :name=, to: :identifier
# info_item_identifier.rb
# This object has a name column in the database
If I'm in the terminal and I have an instance of a Measurement, I can easily call the .name method and it works great. But when it comes to .order(:name), that does not work due to the column not existing.
I have found one solution, but it seems to defeat the purpose of the delegate method. In the controller, I can change it to:
#measurements = Measurement.includes(:identifier).order('info_item_identifiers.name')
Since I know that InfoItem delegates the name method to InfoItemIdentifiers, I don't think my controller needs to know where it's delegated to.
Is there a way to keep the existing code and still order by the delegate method?
Yes, but it requires instantiating all your records first (which will decrease performance).
#measurements = Measurement.joins(:identifier).sort_by &:name
This code loads all Measurements and instantiates them and then sorts them by the Ruby method .name
Explanation
delegate only affects instances of your ActiveRecord model. It does not affect your SQL queries.
This line maps directly to a SQL query.
Measurement.includes(:identifier).order(:name)
As you have noticed, it looks for a name column on the measurements table and doesn't find anything. Your ActiveRecord model instances know that name only exists on identifier, but your SQL database doesn't know that, so you have to tell it explicitly on which table to find the column.

How do I make a query in Ruby on Rails to filter if an object's object's object is nil

I am using Rails 2.3.10. I have models called Stream, Buzz, and BuzzDigest. There is an association, buzz has_many :streams. There is another association, buzz has_one :digest. Sometimes, buzz.digest is nil. How do I write a query for Stream that would filter out streams where the stream's buzz's digest is nil?
This isn't correct syntax, but might be close to what I want:
Stream.find( :all, :conditions => "buzz_id.digest IS NOT NULL" )
Since BuzzDigest is an Object, and not a DB field or attr_accessor, you may want to fix the fact that buzz.digest CAN be nil in the first place within the design of your app.
If you balk at that, quick thought has something like:
Stream.includes[:buzzes, :buzz_digests].group(:id)
assuming the right schema. SO question may have some hints too.

With Rails 2.x, how do I handle a table with a "valid" column?

I've got a table that includes a column named "valid". This has caused a problem after updating to Rails 2. ActiveRecord is expecting "def valid?" to do validation, not return a boolean value from the database.
How do I work around this problem? Is renaming the column my only option?
As documented elsewhere, there are things you can do, but I'm going to suggest that they're probably going to be more trouble in the long run than biting the bullet and renaming the column.
If your database is not open to other apps, that is - otherwise you're just going to suffer to some extent whatever you do...
Why rename? One of the greatest benefits that we get from Rails is convention over configuration. The "magic", if you will. (Some say that it's actually a bad thing, but go with me one this). If you retain a column named "valid", then nyou're making your models inconsistent: this one needs to work differently from the others and that's bad. Or you could monkey-patch ActiveRecord::Base perhaps, so then all your models work the same but your app no longer follows convention.
From personal experience: I created a column named "user_id" which ActiveRecord, by convention, considered a foreign key (as it does anything ending in "_id"). I coded around it, which I now think was a mistake. Another item on the to-do list...
It's not necessarily wrong to go against Rails conventions: there are plenty of places where you can do so and they're well-documented. On the ActiveRecord side, many are specifically designed to reduce difficulty in connecting to legacy database schemas, for example. Take a good look at the pros and cons, as you're obviously doing, and weigh up your options.
I can prevent the crash by adding the following to my model, but it's not entirely satisfactory:
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'valid?'
super
end
end
Do you need to see the column in your model? If not, overriding ActiveRecord::Base.columns will do the trick...
def self.columns
super.delete_if {|c| c.name == 'valid' }
end
You can access the attribute through the [] notation:
row[:valid] = "foo"
You'll get the DangerousAttributeError if you try to initialize an object like this:
row = MyModel.new :valid => "foo"
To prevent that, you can define an attribute setter for valid, like this:
def valid=(x)
self[:valid] = x
end
The valid? method will still be for row validation. You could define a different question method, like val? to get at the boolean, like this:
def val?
query_attribute('valid')
end
Now you can use row.val? to test the boolean

How to Determine if Rails Association is Eager Loaded?

Does anyone know a way to determine if a Rails association has been eager loaded?
My situation: I have a result set where sometimes one of the associations is eager loaded, and sometimes it isn't. If it isn't eager-loaded, then I want to look up associations using ActiveRecord's find. If it is eager loaded, I want to use detect.
For example, say that I have a "has_many" array of shipping_info objects in my item model. Then:
If item is eager loaded, most efficient load is:
item.shipping_infos.detect { |si| si.region == "United States" }
If item isn't eager loaded, most efficient load is:
item.shipping_infos.where(region: "United States").first
But unless I know whether it is eager loaded, I don't know which code to call to get the record efficiently. If I use the first method when it wasn't eager loaded, then I have to look up more DB records than necessary. And if I use the second method when it was eager loaded, then my eager loaded objects are ignored.
Use .association(name).loaded? on a record.
For Rails < 3.1 use loaded_foo?.
(It is deprecated since Rails 3.1. See: https://github.com/rails/rails/issues/472.)
item.shipping_infos.loaded? will tell you.
I gotta say, though: this path leads to madness... before writing code that tests loaded? to decide between #detect and #find, make sure this instance really matters, relative to everything else that's going on.
If this isn't the slowest thing your app does, adding extra code paths adds unnecessary complexity. Just because you might waste a little database effort doesn't mean you need to fix it - it probably doesn't matter in any measurable way.
I'd suggest using item.association_cache.keys that will provide a list of the eager loaded associations. So you item.association_cache.keys.include?(:name_of_association)
association_cached? might be a good fit:
item.association_cached?(:shipping_infos)
You can detect whether or not a single association has been loaded with loaded_foo?. For example, if shipping_info was a belongs_to association, then item.loaded_shipping_info? will return true when it's been eager-loaded. Oddly, it appears to return nil (rather than false) when it hasn't been loaded (in Rails 2.3.10 anyway).
Solution to this problem should be foo.association(:bla).loaded?, BUT it works incorrectly - it checks and marks association as dirty:
class Foo; has_one :bla, :autosave => true end
foo.association(:bla).loaded? #=> false
foo.save # saves foo and fires select * from bla
So I've added following extension to ActiveRecord:
module ActiveRecord
class Base
def association_loaded?(name)
association_instance_get(name).present?
end
end
end
and now:
class Foo; has_one :bla, :autosave => true end
foo.association_loaded?(:bla) #=> false
foo.save # saves foo
Have a look at the Bullet gem.. This will tell you when you should and should not use eager loading.

Resources