Rails4: How to add Computed columns in Active record - sql

We have landed ourself with a column 'data' on User table that has a huge json dump in it.
Now every time we load a bunch of users we get all this data into the memory resulting into out of memory errors.
We want to be able to write some computed columns that we can use in our select statements.
eg:
Instead of doing this
user.data['profile_data']['data']['image']
We would like to add a column :image and then write a query like:
Here :name and :email are actual columns on the table and :image is a computed column:
Users.where(SOME_CONDITION).select(:name,:email,:image)
The main use case is the index page where we display all users which basically loads data column for all users
This will avoid loading the huge data column in memory and help us load the fields we want from the data column
Whats the best way to do this in Rails4?
Updates:
We use postgres on Heroku.

I would move the data column to another table, but if that's not an option try the lazy_columns gem.
class User < ActiveRecord::Base
lazy_load :data
end
Now the data column will be excluded during the initial load, but if you try to access the .data it will be retrieved from the database.

Add the column :image using the migration. Then add the below code :
class User < ActiveRecord::Base
before_save :extract_image
private
def extract_image
self.image = self.data['profile_data']['data']['image']
self.save
end
end
before_save: Is called before Base.save (regardless of whether it’s a create or update save).

Postgresql support two json data type, as described in the documentation
There are two JSON data types: json and jsonb. They accept almost identical sets of values as input. The major practical difference is one of efficiency. The json data type stores an exact copy of the input text, which processing functions must reparse on each execution; while jsonb data is stored in a decomposed binary format that makes it slightly slower to input due to added conversion overhead, but significantly faster to process, since no reparsing is needed. jsonb also supports indexing, which can be a significant advantage.
So to solve your problem, you need to change the type of the data column to jsonb through a migration:
# This should use the up and down methods, because change_column
# is not reversible
class ChangeUsersDataColumnTypeToJsonb < ActiveRecord::Migration
def up
change_column :users, :data, :jsonb
end
def down
change_column :users, :data, :text # or whatever datatype it was
end
end
than to query the image field with you use the functions that postgres provides to query the json data type :
Users.where(SOME_CONDITION).select(:name,:email,"data::json->'image' as image")
than you access the image attribute like any other attribute.
You have also to define the :data attribute as a lazy loading column, like , so that column do not get loaded when the user object is instantiated.
class User < ActiveRecord::Base
lazy_load :data
end

Related

Query STI And Return List Containing Specific Types

I have a typical single table inheritance case, something like:
class Instrument < ActiveRecord::Base
def self.search(params)
query = Instrument
# build up the query scope
query
end
end
class Guitar < Instrument
end
class Trumpet < Instrument
end
I need to get a single paginated list of instruments of all kinds.
#instruments = Instrument.search(params)
.page(params[:page])
.per(params[:size])
My problem is that each model in the search results is an Instrument, whereas I need them to be Guitars and Trumpets, etc or else subclass methods like #blow and #strum are missing.
Because of pagination, I can't really do multiple queries.
Surely this is a common use case that ActiveRecord supports, and I'm just missing something obvious, yes?
Have you have added a type column to the instruments table, migrated the database and restarted the server?
Any Instrument object that is loaded from the database, assuming it has a type set, should automatically be loaded as the subclass.
Are you positive the values in the type column are being stored correctly?
I believe the class name in the type column is case-sensitive.

ruby on rails multiple tables in one model

I am using Ruby on Rails and I have to create an importer from one database to another. There are over 100 tables in each database and I don't want to create a model for each table. Is there any possibility to create queries, specifying a table name? I don't want a model bind to a table.
For example: FirstDatabase.select('*').from('some_table').where(...)
Without set_table_name ... just dynamic models
Also I need to do inserts in different tables
I would not use models for that task at all. Instead, use the #select_all, #exec_insert, #exec_update, and #exec_delete methods of the base connection as appropriate.
ActiveRecord::Base.connection.select_all("select * from #{table_name}")
Returns an array of hashes for rows.
ActiveRecord::Base.connection.exec_insert("insert into #{table} (foo, bar) values(#{foo}, #{bar})
Inserts a row. The values will need to be properly escaped strings, dates, etc. for whatever database you are using.
ActiveRecord::Base.connection.quote("fo'o")
=> "'fo''o'" # When server is PostgreSQL
Returns a quoted string representation suitable for use in a SQL statement.
now=Time.now
=> Fri Aug 23 02:24:40 -0700 2013
ActiveRecord::Base.connection.quote(now)
=> "'2013-08-23 09:24:40.365843'"
Returns a quoted date/time representation in UTC timezone.
To deal with the multiple databases, you can do something like set up a single model for each, and then get connections from those models instead of from ActiveRecord::Base.
class TableInDbA << ActiveRecord::Base
establish_connection "database_a_#{Rails.env}"
end
class TableInDbB << ActiveRecord::Base
establish_connection "database_b_#{Rails.env}"
end
TableInDbA.connection.select_all("...

Get all data from all tables (Postgresql)

data = Program.joins(:program_schedules, :channel).
where(
"program_schedules.start" => options[:time_range],
"programs.ptype" => "movie",
"channels.country" => options[:country]).
where("programs.official_rating >= ?", options[:min_rating]).
group("programs.id").
order("programs.popularity DESC")
This query retrieve only the "programs" table (I think because the "group by" clause).
How I could retrieve all data from all tables (programs, programs_schedules, channel) ?
class Program < ActiveRecord::Base
belongs_to :channel
has_many :program_schedules
Ruby on Rails 3.2
Postgresql 9.2
Are you looking for the eager loading of associations provided by the includes method ?
Eager loading is a way to find objects of a certain class and a number of named associations. This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2.
Update:
You can get the SQL as a string by appending .to_sql to your query. .explain can also help.
If you want to get the data as a hash and no more as model instances, you can serialize the result of the query and include the associations using .serializable_hash :
data.serializable_hash(include: [:program_schedules, :channel])
You can define which fields to include using :except or :only options in the same way as described here for .as_json (which acts similarly) :
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Generating serial numbers in Rails ActiveRecord

In a Rails app I need a source of unique, sequential (no gaps) integers to use as serial numbers. It must be persistent and allow concurrent access.
Database auto-increment isn't adequate because most don't guarentee the "no gaps" property.
In straight SQL I would just create a one-line table and say (in PostgreSQL) something like:
update sequence set value = value + 1 returning value
This is apparently standard practice in the SQL world. References exist.
In ActiveRecord I easily created a model the model and found .increment! and .increment_counter in the documentation. But I can't figure out how to atomically retrieve the incremented value. Locks and transactions don't seem to help.
Since update ... returning acts like a select for output purposes, it turns out you can use find_by_sql to both update and get the updated value in one operation.
class SequenceNumber < ActiveRecord::Base
attr_accessible :tag, :value
validates :tag, :uniqueness => true
def self.get_next(tag)
find_by_sql("update sequence_numbers
set value = value + 1
where tag = '#{tag}'
returning value").first.value
end
end
The remaining problem is that this is totally non-portable because returning is a pgsql extension. Maybe the ActiveRecord developers will notice this.
If you want to use redis (and maybe you already are because of Resque or Sidekiq), you can do an INCR on a key, this is atomic and returns the new value.

Allow users to create dynamic model attributes?

In my Rails3 app, I am using ActiveRecord and Postgresql.
Say I have a model called Parts. The model has a small list of standard attributes such as price, quantity, etc.
However, Customer A might want to add LotNumber and CustomerB might want to add OriginalLocation.
How would I allow them to do that?
I thought about creating a PartsDetail model that allowed them to have a type.
class PartsDetail < ActiveRecord::Base
attr_accessible :type, :value, :part_id
belongs_to :parts
end
So that "type" could be "LotNumber", etc.
But I'm not quite sure how that would work in my associations and querying.
Any ideas?
Thanks.
Since you're using PostgreSQL, you could use hstore to store arbitrary hashes in database columns:
This module implements the hstore data type for storing sets of key/value pairs within a single PostgreSQL value. This can be useful in various scenarios, such as rows with many attributes that are rarely examined, or semi-structured data. Keys and values are simply text strings.
There's even a gem for adding hstore support to ActiveRecord:
https://github.com/softa/activerecord-postgres-hstore
Then you could create an hstore column called, say, client_specific and look inside it with things like:
M.where("client_specific -> 'likes' = 'pancakes'")
M.where("client_specific #> 'likes=>pancakes'")
to see which clients have noted that they like pancakes.
You might want to store a list of customer-specific fields somewhere with the customer record to make the UI side of things easier to deal with but that's pretty simple to do.
Here's a gist that someone wrote that allows you to use hstore attributes in your models more seamlessly: https://gist.github.com/2834785
To use add this in an initializer ( or create a new initializer called active_record_extensions.rb )
require "hstore_accessor"
Then in your model you can have:
Class User < ActiveRecord::Base
hstore_accessor :properties, :first_name, :last_name
end
That allows you to do:
u = User.new
u.first_name = 'frank'
You can still do add attributes to the hstore column and bypass the hstore_attributes though:
u.properties['middle_name'] = 'danger'