Sort by state in database - ruby-on-rails

Given I have a model house and it lives through several states, something like this:
Dreaming —> Planning —> Building —> Living —> Tearing down
If I would want to retrieve let's say ten houses from the databse and order them by the state field, I'd get first all houses in the Building state, then Dreaming, then Living, …
Is it possible to fetch all houses from the database and order them by the state in the order intended before retrieving them? Meaning, first all houses in the Dreaming state, then Planning, etc. E.g. by providing the order in an array for comparison of sorts.
I'd like to avoid doing this in Ruby after having fetched all entries as well as I wouldn't want to use IDs for the states.
After reading up on enum implementations, I guess, if I can make it work, I'll try to combine the enum column plugin with the state_machine plugin to achieve what I'm after. If anyone has done something like this before (especially the combination under Rails 3), I'd be grateful for input!

Here's some information on how to use SQL ENUMs in rails -- they're relatively database portable and do roughly what you want -- http://www.snowgiraffe.com/tech/311/enumeration-columns-with-rails/

If you are using MySQL, then the solution is to do ORDER BY FIELD(state, 'Building', 'Dreaming', 'Living', ...):
House.order("FIELD(state, 'Building', 'Dreaming', 'Living', ...)")

If you want to order the collection after a certain criteria then you must store that criteria somewhere.
I don't know if this goes against your "not in Ruby" criteria but I would probably do something like this:
class House < ActiveRecord::Base
STATES { 0 => "Dreaming",
1 => "Planning",
2 => "Building",
3 => "Living",
4 => "Tearing Down" }
validates_inclusion_of :state, :in => STATES.keys
def state_name
STATES[self.state]
end
end
#houses = House.order("state")
In this case the db field state is an integer instead of a string. It makes it very effective for database storage as well as querying.
Then in your view, you call state_name to get the correct name from the STATES hash stored in the model. This can also be changed to use i18n localization by using labels instead of strings in the hash.

Related

In Rails, what are the disadvantages of using model methods for constants, instead of Active Record columns?

I'm building a Rails app that will have a very high number of models using single-table inheritance. For the model subclasses, I want to be able to set constant values for things like name and description. I know I can set defaults using attribute, like this:
class SpecialWidget < Widget
attribute :name, :string, default: "Special Widget"
attribute :description, :text, default: "This is an awfully important widget."
end
The advantage here, as I understand it, is that by storing the defaults in the database, I retain the ability to do things use #order to sort by name, and paginate. But it seems bad to store constants in the database like that. It seems better to use constant methods, like this:
class SpecialWidget < Widget
def name
"Special Widget"
end
def description
"This is an awfully important widget."
end
end
In fact, that's what I was doing originally, but then I read posts like these (one, two, three), which pointed out that then if I wanted to do nice things like sort by the methods, I'd have to load the entire Widget.all into memory and then do a plain-old Ruby sort.
My application is built quite heavily around these STI models, and I will definitely have to sort by constants like name. Are the concerns about sorting and pagination significant disadvantages that will cause me to come to regret using methods in the future, or will the difference be negligible? What other disadvantages/problems might I have? I'd really like to be able to use methods instead of storing constants in the database, if possible without crippling my app's performance.
There are many benefits and few downsides to storing the default values in the database. But if it troubles you, you can have similar sorting efficiency by constructing your sort like this:
class SpecialWidget < Widget
DefaultAttrs = {name: 'Special Widget', description: 'This is... etc'}
end
class Widget < ApplicationRecord
def self.sort_by_name
types = pluck(:type).uniq
case_statements = types.map{|type| "WHEN '#{type}` THEN `#{type.constantize.const_get(:'DefaultAttrs')[:name]}'"
case_sql = "CASE type #{case_statements.join(' ') END"
order(case_sql)
end
end
... not very elegant, but it does the job!
maybe better to put the constants in the database!
It depends entirely on the shape of your data and how you want to use it. You haven't provided enough contextual specifics to guarantee that my recommendation applies to your situation, but it's a recommendation that's specifically designed to work for 95+% of all situations.
Just Put the Data in the Relational Database
The database is the store for all things in your domain that is dynamic and needs to be persisted, i.e. state. It should be internally consistent, meaningfully self-descriptive, and well-structured in order to fully leverage the power of a relational db to flexibly manipulate and represent complex inter-related data.
Based on what you've said, and assuming that there are a bunch of different "widget types" implemented using Rail's STI implementation with a type column, I would model Widget and SpecialWidget in the database like this:
widgets
id | type
-------------------
1 | 'Widget'
2 | 'SpecialWidget'
3 | 'Widget'
4 | 'Widget'
widget_types
type | name | description
--------------------------------------------------------------
'Widget' | 'Normal Widget' | 'A normal widget.'
'SpecialWidget' | 'Special Widget' | 'This is an awfully important widget.'
You called these values a "constant", but are they really? In the purposes of your domain, will they never change like the value of Matth::PI never changes? Or will descriptions be changed, widgets renamed, widgets added, and widgets expired? Without knowing for sure I'm going to assume they're not actually Constant.
Having name and description as methods is effectively storing that widget_types table in your application source code, moving data out of your database. If you really can't afford the extra millisecond a simple JOIN for two small strings on each Widget incurs, then just load the full widget_types table into cache once on application startup, and it'll perform the same as saving it in source code.
This schema is more normalized (incurring benefits), the data itself describes all I need to know, and as you've pointed out, I can flexibly operate on that data (important since you "will definitely have to sort"). The data in this form is also extensible for future changes as they come.
Again: the database stores structured data for the purpose of on-demand flexible manipulation -- you can make up queries on the fly, and the DB can answer it.
I Really Don't Want to Put Data in the Database
Okay... then you'll have to pass that data into the database every time you want to operate on it. You can do it like so:
SELECT w.id, w.type, wt.name
FROM widgets w
INNER JOIN (
VALUES ('Widget', 'Normal Widget'), ('SpecialWidget', 'Special Widget')
) wt(type, name) ON wt.type = w.type
ORDER BY wt.name
The VALUES expression creates an ad-hoc table mapping the class to the name. By passing in that mapping and joining on it (every time), you can tell the DB to ORDER BY it.

How to group by multiple attributes on children, and then count?

In a Rails 3.2 app I have a User model that has many Awards.
The Award class has :type, :level and :image attributes.
On a User's show page I want to show their Awards, but with some criteria. User.awards should be grouped by both type and level, and for each type-level combination I want to display its image, and a count of the awards.
I'm struggling to construct the queries and views to achieve this (and to explain this clearly).
How can I group on two attributes of a child record, and then display both a count and attribute (i.e. image) of those children?
It took me some time to figure this out because of the complicated mix of active record objects, arrays and grouped arrays.
Anyway, incase this is useful for anyone else
Given a User has many Awards, and Award has attributes :type, :level, :image.
for award in #user.awards.group_by{ |award| [award.type,award.level] }.sort_by{|award| [award[0][0], award[0][1]]}
puts "#{(award[0][0]).capitalize} - Level #{award[0][1]}" # e.g. Award_Name - Level 1
puts award[1].first.image #outputs the value of award.image, i.e. the image url
puts award[1].count #counts the number of grouped awards
end
A bit fiddly! Maybe there are ways to optimize this code?
Depending on the database you're using you have to build a custom SQL query using a GROUP BY on type and level:
SELECT * FROM users GROUP BY users.type, users.level
(Postgres has a special interpretation of the GROUP BY so check the document of the database you're using).
To write it in Rails read the documentation: http://guides.rubyonrails.org/active_record_querying.html#group
For the count you'll have to do it in a second step (Ruby could do it using the size method on the Array of ActiveRecord object the query will return you).

Rails ActiveRecord - Uniqueness and Lookup on Array Attribute

Good morning,
I have a Rails model in which I’m currently serializing an array of information. Two things are important to me:
I want to be able to ensure that this is unique (i.e. can’t have two models with the same array)
I want to be able to search existing models for this hash (in a type of find_or_create_by method).
This model describes a “portfolio” – i.e. a group of stock or bonds. The array is the description of what securities are inside the portfolio, and in what weights. I also have a second model, which is a group of portfolios (lets call it a “Portcollection” to keep things simple). A collection has many portfolios, and a portfolio can be in many collections. In other words:
class Portfolio
serialize :weights
has_and_belongs_to_many :portcollections
class Portcollection
has_and_belongs_to_many :portfolios
When I am generating a “portcollection” I need to build a bunch of portfolios, which I do programmatically (implementation not important). Building a portfolio is an expensive operation, so I’m trying to check for the existence of one first. I thought I could do this via find_or_create_by, but wasn’t having much luck. This is my current solution:
Class Portcollection
before_save :build_portfolios
def build_portfolios
……
proposed_weights = ……
yml =proposed_weights.to_yaml
if port = Portfolio.find_by_weights(yml)
self.portfolios << port
else
self.portfolios << Portfolio.create!(:weights => proposed_weights)
end
……..
end
This does work, but it is quite slow. I have a feeling this is because I’m converting stuff to YAML each time it runs when I try to check for an existing portfolio (this is running probably millions of times), and I’m searching for a string, as opposed to an integer. I do have an index on this column though.
Is there a better way to do this? A few thoughts had crossed my mind:
Calculate an MD5 hash of the “weights” array, and save to a database column. I’ll still have to calculate this hash each time I want to search for an array, but I have a gut feeling this would be easier for the database to index & search?
Work on moving from has_and_belongs_to_many to a has_many => through, and store the array information as database columns. That way I could try to sort out a database query that could check for the uniqueness, without any YAML or serialization…
i.e. something like :
class Portfolio
has_many :portcollections, :through => security_weights
class Portcollections
has_many :portfolios, :through => security_weights
SECURITY_WEIGHTS
id portfolio_id portcollection_id weight_of_GOOG weight_of_APPLE ……
1 14 15 0.4 0.3
In case it is important, the “weights” array would look like this:
[ [‘GOOG’, 0.4] , [‘AAPL’, 0.3] , [‘GE’, 0.3] ]
Any help would be appreciated. Please keep in mind I'm quite an amateur - programming is just a hobby for me! Please excuse me if I'm doing anything really hacky or missing something obvious....
Thanks!
UPDATE 1
I've done some research into the Rails 3.2 "store" method, but that doesn't seem to be the answer either... It just stores objects as JSON, which gives me the same lack of searchability I have now.
I think storing a separate hash in it's own column is the only way to do this efficiently. You are using serialization or a key/value store that is designed to not be easily searchable.
Just make sure you consider sorting on your values before hashing them, other wise you could have the same content but differing hashes.

How do you normally sort items in Rails?

I have a little example Rails app called tickets, which views and edits fictional tickets sold to various customers. In tickets_controller.rb, inside def index, I have this standard line, generated by scaffolding:
#tickets = Ticket.find(:all)
To sort the tickets by name, I have found two possible approaches. You can do it this way:
#tickets = Ticket.find(:all, :order => 'name')
... or this way:
#tickets = Ticket.find(:all).sort!{|t1,t2|t1.name <=> t2.name}
(Tip: Ruby documentation explains that sort! will modify the array that it is sorting, as opposed to sort alone, which returns the sorted array but leaves the original unchanged).
What strategy do you normally use? When might you use .sort! versus the :order => 'criteria' syntax?
Use :order => 'criteria' for anything simple that can be done by the database (ie. basic alphabetical or chronological order). Chances are it's a lot faster than letting your Ruby code do it, assuming you have the right indexes in place.
The only time I could think you should use the sort method is if you have a complex attribute that's calculated at run-time and not stored in the database, like a 'trustworthiness value' based off number of good/bad responses or something. In that case it's better to use the sort method, but be aware that this will screw things up if you have pagination in place (each page will have ITS results in order, but the set of pages as a whole will be out of order).
I specify an order in the ActiveRecord finder or in the model association because sorting using SQL is faster. You should take advantage of the features offered by the RDBMS when you're able to do so.

Best way to store constants referenced in the DB?

In my database, I have a model which has a field which should be selected from one of a list of options. As an example, consider a model which needs to store a measurement, such as 5ft or 13cm or 12.24m3. The obvious way to achieve this is to have a decimal field and then some other field to store the unit of measurement.
So what is the best way to store the unit of measurement? I've used a couple of approaches in the past:
1) Storing the various options in another DB table (and associated model), and linking the two with a standard foreign key (and usually eager loading the associated model). This seems like overkill, as you are forcing the DB to perform a join on every query.
2) Storing the options as a constant Hash, loaded in one of the initializers, where the key into the Hash is stored in the unit of measurement field. This way, you effectively do the join in Ruby (which may or may not be a performance increase), but you lose the ability to query from the "unit of measurement" side. This wouldn't be a problem provided it's unlikely you'd need to do queries like "find me all measurements with units of cm".
Neither of these feel particularly elegant to me.. can anyone suggest something better?
Have you seen constant_cache? It's sort of the combination of the best of 1 and 2 - lookup data is stored in the DB, but it's exposed as class constants on the lookup model and only loaded at application start, so you don't suffer the join penalties constantly. The following example comes from the README:
migration:
create_table :account_statuses do |t|
t.string :name, :description
end
AccountStatus.create!(:name => 'Active', :description => 'Active user account')
AccountStatus.create!(:name => 'Pending', :description => 'Pending user account')
AccountStatus.create!(:name => 'Disabled', :description => 'Disabled user account')
model:
class AccountStatus < ActiveRecord::Base
caches_constants
end
using it:
Account.new(:username => 'preagan', :status => AccountStatus::PENDING)
I would go with option one. How large will it be the UnitOfMeasurement table? And, if using an integer primary key, why do you worry so much about speed?
Option 1 is the way to go for design reasons. Just declare it with an integer (even smallint) primary key and a field for the unit description.
Has ActiveRecord gotten support for natural keys, yet? If it has, you can just make the name (or whatever) column of the UnitOfMeasure table the PK, that way the value of the FK column has all the info you need, and you still have a fully normalized DB with a canonical set of UnitOfMeasurement values.
Do you need to perform lookups on these values? If not, you could as well store them as a string and parse the string later on in the application that reads the values. While you risk storing unparseable data, you gain speed and reduce DB complexity. Sometimes normalizing a database is not helpful. In the end /something/ within your system needs to know that "cm" is a length measure and "m3" is a room measure and comparing "3cm" to "1m3" doesn't make any sense anyway. So you just as well can put all that knowledge in code.
Let's say you are only going to display that data anyway, what is normalizing good for here?

Resources