Managing the default order of a table in Rails 3 - ruby-on-rails

I have a model which has over 40,000 entires in it. I want to be able to have this table permanently sorted by one of its attributes. The tricky part of this is that some of the elements have a nil value for the attribute I want to sort by.
Some poking around has led me to default_scope, but it appears this is being deprecated and everyone warns against it. It seems like putting default_scope order('director_id DESC') or something like this would fix things, but this doesn't take into account nil values. What is the better alternative?
Thanks!
EDIT
I'm also using Tire with ElasticSearch for managing searches.

Yes, it's best to be explicit with model scopes. You can just do:
class MyModel < ActiveRecord::Base
def self.default_order
order('director_id DESC NULLS LAST')
end
end
Your database will have a syntax as part of ORDER BY for the placement of NULL values. If you don't want NULL values in the output at all then you can add a where call and the method should be renamed.

Related

Ruby on Rails has_many association define custom ordering

I have run into a problem and can't figure out how to fix it.
I basically have a ModelA that has a has_many relationship with another ModelB. Let's say that ModelB has two attributes. A created_at and a filename. Now let's say the created_at is not actually accurate due to a race condition. However the filename contains a timestamp that is accurate. Unfortunately the filename is also inconsistent and extracting and adding the timestamp beforehand is not an option.
Is there any way to do something like this?
class ModelA
has_many: model_bs, order: extracted_timestamp_from_filename.asc
def extracted_timestamp_from_filename
#self would ideally be a single model_bs
extract_timestamp_from_filename(self.filename)
end
end
At least with order I don't think this could ever work without having an actual proper timestamp attribute. Is there a way to get this functionality? If there is a way to do this with hooks somehow I haven't found it.
This is not really a fix but turns out Rails with a MongoDB is actually able to order by strings and it so happens that the files in my example can be ordered like this. However if the filenames have different formats this will still not work. In my example I think I'll get away with it for now.
Thanks.

Validate uniquness in ActiveRecord on part of a string

Lets say I have registration_id attribute on Dummy model.
Its data type is string.
Its length can be anything from 10-14 characters but I want to put a uniqueness validation on last 10 characters only. (weird but true)
Now how can I achieve this?
What I have thought of:
Create another attribute last_ten_chars_registration_id in Dummy table to hold last 10 characters and put uniqueness on this attribute.
(As Computed attributes apparently don't work for uniqueness validations)
I can create a custom validator and write a query.
I am not sure (may be like query)
Can anyone suggest me any better way to achieve this?
You can use a custom validator like this.
class Dummy < ActiveRecord::Base
validates_with RegistrationValidator, :fields => [:registration_id]
# Whatever else...
end
class RegistrationValidator < ActiveModel::Validator
def validate(record)
reg_id = record.registration_id.last(10).join
if Dummy.where('registration_id LIKE ?',"%#{reg_id}")
record.errors[:registration_id] << "Registration ID taken!"
end
end
end
Same as GoGoCarl, I also think it largely depends on the performance you require. A custom query (not using LIKE but rather the RIGHT(registration_id, 10) function, at least in MySQL) will I think do fine unless the Dummy table is huge or you need the query to be super fast. In that case, I too would do the special column with the last 10 chars, and an accompanying db index.
A pragmatic solution might be to first go the custom query route as it seems simpler to implement to me, and later, if performance starts to suffer, switch to the special column.
You could also do your own benchmarks, e.g. populate a test Dummy table with the number of records you expect and see for yourself if the performance is OK with you or not.
See also this related SO question on string SQL functions performance where the solutions are similar.

Rails scope complexity

I have a model to which I need to create a default scope. I am unsure of the best way to write this scope but I will explain how it needs to work.
Basically I need to get all items of the model and if two items have the same "order" value then it should look to the "version" field (which will contain, 1, 2, 3 etc) and pick the one with the highest value.
Is there a way of achieving this with just a scope?
Try this code:
scope :group_by_order, -> { order('order ASC').group('order') }
default_scope, { (group_by_order.map{ |key,values| values.order('version DESC') }.map{|key, values| values - values[1..-1]}).values.flatten }
Explanation Code:
order by "order" field.
group by "order" field.
map on the result hash, and order each values by "version" field
map again on values, and remove from index "1" to the end.
get all values, and flatten them
A word of caution using default scopes with order. When you performs updated on the collection such as update_all it will use the default scope to fetch the records, and what you think would be a quick operation will bring your database to its knees as it copies the rows to a temporary table before updating.
I would recommend just using a normal scope instead of a default scope.
Have a look at Select the 3 most recent records where the values of one column are distinct on how to construct the sql query you want and then put that into a find_by_sql statemate mentioned in How to chain or combine scopes with subqueries or find_by_sql
The ActiveRecord order method simply uses the SQL ORDER function which can have several arguments. Let's say you have some model with the attributes order and version then the correct way order the records as you describe it, is order(:order, :version). If you want this as the default scope would you end up with:
default_scope { order(:order, :version) }
First, default_scopes are dangerous. They get used whenever you use the model, unless you specifically force 'unscoped'. IME, it is rare to need a scope to every usage of a model. Not impossible, but rare. And rarer yet when you have such a big computation.
Instead of making a complex query, can you simplify the problem? Here's one approach:
In order to make the version field work, you probably have some code that is already comparing the order fields (otherwise you would not have unique rows with the two order fields the same, but the version field differing). So you can create a new field, that is higher in value than the last field that indicated the right entity to return. That is, in order to create a new unique version, you know that you last had a most-important-row. Take the most-important-rows' sort order, and increment by one. That's your new most-important-rows' sort order.
Now you can query for qualifying data with the highest sort order (order_by(sort_order, 'DESC').first).
Rather than focus on the query, focus on whether you are storing the right data, that can the query you want to achieve, easier. In this case, it appears that you're already doing an operation that would help identify a winning case. So use that code and the existing database operation, to reduce future database operations.
In sql you can easily order on two things, which will first order on the first and then order on the second if the first thing is equal. So in your case that would be something like
select * from posts order by order_field_1, version desc
You cannot name a column order since it is a sql reserved word, and since you did not give the real column-name, I just named it order_field_1.
This is easily translated to rails:
Post.order(:order_field_1, version: :desc)
I would generally advice against using default_scope since once set it is really hard to avoid (it is prepended always), but if you really need it and know the risks, it is really to apply as well:
class Post < ActiveRecord::Base
default_scope { order(:order_field_1, version: :desc) }
end
This is all actually documented very well in the rails guides.

Rails Hide Specific Record From All Select * Type Queries

I have a record in a table that serves as a placeholder of sorts, and doesn't represent actual data. It's bad design, I know, but I have some very awkward requirements that I have to deal with and I saw no other solutions so it's a bit of a hotfix per se.
Now lets say I have a series of SELECT *s throughout my application and I don't want to have to explicitly exclude that single record for each of them. Is there anything I can drop into my model to exclude it from all queries except for the ones where it's explicitly called? Or perhaps some logic I can put directly into my PG database?
It's the very first record in the table with an ID of 0.
Add a default scope
default_scope where('id != 0')
to your model...
In any case you want to avoid that default scope in some query, you can have Model.unscoped... there...
One solution would be to define a default_scope that would exclude those records, see the doc
So when doing YourModel.all, if the default_scope on YourModel excludes the correct records, you'll get what you want.
But as you said, it's bad design !
Create a view excluding it:
create view v as
select *
from t
where id != 0
Now select from the view:
select *
from v

Order users in database, so I don't have to do order_by each time I query

Can I order my users in the database, so I don't have to say order_by("created_at desc") each time I query?
Sounds for me like a logical thing to do, but I don't know if it's possible and if it's best practice?
SOLUTION
I'm already using the default_scope and as I understand it from you, it is the best way to do it? Thanks a lot for the answers though.
If you are after results sorted by create date desc, the reverse natural order will be close to this (but not guaranteed to be identical).
If you want a specific ordering, adding order_by() to an indexed query is the best way to assure this.
If you are using the default generated ObjectIds the first 4-bytes are actually a unix timestamp (seconds since the epoch) .. and the _id field is indexed by default aside from a few exceptions noted in the documentation.
So a query like last 50 users created (based on ObjectId) in the mongo shell would be:
db.users.find().sort({_id:-1}).limit(50)
There are mixed views about default scopes, but to achieve what you're asking:
http://apidock.com/rails/ActiveRecord/Base/default_scope/class
class User < ActiveRecord::Base
default_scope order('created_at DESC')
### other model code here ###
end
you should be able to add an index or indexes to your db table. Be careful with running this on a live system as the overhead for creating an index on a large table can be disabling.
EDIT: should have expanded.
By creating an index, you will still have to order, but your ordering/sorting will be more efficient.
ref: Is it okay to add database indexes to a database that already has data?

Resources