How to use a postgresql function in Rails - ruby-on-rails

I have a table in postgres with date_of_birth and would like to use the Postgres age function to return the age, rather than calculate the age in Rails (this is a simple example, I will want to do more complicated calculations in postgres).
What is the best way to get the age back from postgres with the rest of my record, ideally as standard without having to modify every select?
EDIT:
I've decided to use views because:
My database will be used by other applications other than rails and I want to define common functions that all of them can use.
I'd like to control access to the data over multiple applications.
It takes some of the processing away from the application server.
It is more scalable if I use a lot of calculated fields.

people = Person.select('*, age(date_of_birth)')
people.each { |person| puts person.age }
Yes, this will add a method, age, that did not previously exist on Person
You can also alias the new method to something other than the function name:
people = Person.select('*, age(date_of_birth) as foo')
people.each { |person| puts person.foo }

Related

Ruby: Hash: use one record attribute as key and another as value

Let's say I have a User with attributes name and badge_number
For a JavaScript autocomplete field I want the user to be able to start typing the user's name and get a select list.
I'm using Materialize which offers the JS needed, I just need to provide it the data in this format:
data: { "Sarah Person": 13241, "Billiam Gregory": 54665, "Stephan Stevenston": 98332 }
This won't do:
User.select(:name, :badge_number) => { name: "Sarah Person", badge_number: 13241, ... }
And this feels repetitive, icky and redundant (and repetitive):
user_list = User.select(:name, :badge_number)
hsh = {}
user_list.each do |user|
hsh[user.name] = user.badge_number
end
hsh
...though it does give me my intended result, performance will suck over time.
Any better ways than this weird, slimy loop?
This will give the desired output
User.pluck(:name, :badge_number).to_h
Edit
Though above code is one liner, it still have loop internally. Offloading such loops to database may improve the performance when dealing with too many rows. But there is no database agnostic way to achieve this in active record. Follow this answer for achieving this in Postgres
If your RDBMS is Postgresql, you can use Postgresql function json_build_object for this specific case.
User.select("json_build_object(name, badge_number) as json_col")
.map(&:json_col)
The whole json can be build using Postgresql supplied functions too.
User.select("array_to_json(array_agg(json_build_object(name, badge_number))) as json_col")
.limit(1)[0]
.json_col

How to add attribute/property to each record/object in an array? Rails

I'm not sure if this is just a lacking of the Rails language, or if I am searching all the wrong things here on Stack Overflow, but I cannot find out how to add an attribute to each record in an array.
Here is an example of what I'm trying to do:
#news_stories.each do |individual_news_story|
#user_for_record = User.where(:id => individual_news_story[:user_id]).pluck('name', 'profile_image_url');
individual_news_story.attributes(:author_name) = #user_for_record[0][0]
individual_news_story.attributes(:author_avatar) = #user_for_record[0][1]
end
Any ideas?
If the NewsStory model (or whatever its name is) has a belongs_to relationship to User, then you don't have to do any of this. You can access the attributes of the associated User directly:
#news_stories.each do |news_story|
news_story.user.name # gives you the name of the associated user
news_story.user.profile_image_url # same for the avatar
end
To avoid an N+1 query, you can preload the associated user record for every news story at once by using includes in the NewsStory query:
NewsStory.includes(:user)... # rest of the query
If you do this, you won't need the #user_for_record query — Rails will do the heavy lifting for you, and you could even see a performance improvement, thanks to not issuing a separate pluck query for every single news story in the collection.
If you need to have those extra attributes there regardless:
You can select them as extra attributes in your NewsStory query:
NewsStory.
includes(:user).
joins(:user).
select([
NewsStory.arel_table[Arel.star],
User.arel_table[:name].as("author_name"),
User.arel_table[:profile_image_url].as("author_avatar"),
]).
where(...) # rest of the query
It looks like you're trying to cache the name and avatar of the user on the NewsStory model, in which case, what you want is this:
#news_stories.each do |individual_news_story|
user_for_record = User.find(individual_news_story.user_id)
individual_news_story.author_name = user_for_record.name
individual_news_story.author_avatar = user_for_record.profile_image_url
end
A couple of notes.
I've used find instead of where. find returns a single record identified by it's primary key (id); where returns an array of records. There are definitely more efficient ways to do this -- eager-loading, for one -- but since you're just starting out, I think it's more important to learn the basics before you dig into the advanced stuff to make things more performant.
I've gotten rid of the pluck call, because here again, you're just learning and pluck is a performance optimization useful when you're working with large amounts of data, and if that's what you're doing then activerecord has a batch api you should look into.
I've changed #user_for_record to user_for_record. The # denote instance variables in ruby. Instance variables are shared and accessible from any instance method in an instance of a class. In this case, all you need is a local variable.

ActiveRecord scope cache for remote attribute?

I have a large project, there are several separated db instance, database name and different table names, all need to be processed in single one Sinatra controller.
Something like this
class ItemLog < ActiveRecord::Base
def address
http_get('http://x.com/get_address_by_item_type?id=' + self.type_id).to_json['address']
end
end
so if I iterated through a long list of ItemLog, the http get called multiple times per each ItemLog.
But since ItemLog.type_id is a limited set, but it's saved in a remote db, It's ideal that for each unique type_id, we only need to issue exactly one http request.
I know how to declare a standalone hash for caching like this:
address_cache = Hash[ItemLog.where(...).limit(500).pluck(:type_id).uniq.map {|type_id| [type_id, get_address(type_id)] }]
ItemLog.where(...).limit(500).each { |item| item.address = address_cache[item.type_id }
My question is is there anyway to write more elegant code for this? Something like "scope cache" works like a {item_type_id: address_string} pairs, but directly in a encapsulated ActiveRecord model

How to search for wild card in Rails

Im trying to search my User model for all Users that start with any integer, I have code for individual letters and it works, but Im having trouble getting it working with a wild card. Right now I have this code:
in my view:
<%= link_to '#', users_charlist_path(:char => '[0123456789]' %>
and in my controller I have:
def charlist
#a = User.where('goal like ?', "#{params[:char]}%").to_a
end
how ever, '[0123456789]', doesnt seem to work as it does not return anythign to me even though I have users whose names begin with an integer. how do i do this?
The where method is a part of ActiveRecord which maps the objects to the database. So how you can query the database with a regex depends on which db you are using not on ruby. You need to look up the regex functions of the database your using. For mysql you can find them here:
http://dev.mysql.com/doc/refman/5.1/de/regexp.html
An alternative is to select all objects an use the select method to filter the results that match your needs. That method is documented here:
http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-select
On big amounts of data I whould suggest to use the database even if that means your application isnt 100% portable between different database systems.

ruby on rails find_or_initialize

Is there any better way to achieve this in Ruby on Rails?
I'm searching for 11 fields and all are required also, and if not found initialize it.
There will be more required fields adding to it.
This query works perfect for me, but it just doesn't look like the best way to do this.
find_or_initialize_by_make_and_country_and_engine_and_power_and_body_and_doors_and_fuel_and_cylinders_and_transmission_and_gears_and_wheels(model,country,engine,power,body, doors, fuel, cylinders, transmission,gears,wheels)
On rails 3.2 I would do
attributes = {}
Model.where(attributes).first_or_initialize
Documentation http://apidock.com/rails/ActiveRecord/Relation/first_or_initialize
Considering the sheer number of fields you are using, you probably are better off manually finding the record and initializing it if it does not exist:
attributes = {
country: country,
engine: engine,
power: power,
body: body
etc ...
}
record = where(attributes).first
record = new(attributes) unless record
You can define method like this in your model
def self.find_or_initialize_by_field(params)
send("find_or_initialize_by_#{params.keys.join("_and_")}", *params.values)
end
and then you can call this method like
YourModel.find_or_initialize_by_field({country: country, engine: engine})
while using find_or_intialize_by
find by key fields , dont assign all the feilds at once
eg I assume the make is the key field in the model
car = find_or_initialize_by_make(model)
car.update_attributes(country: country,engine: engine,power: power, body: body,doors: doors ,fuel: fuel,cylinders:cylinders,transmission: transmission, gears: gears, wheels: wheels)
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Dynamic+attribute-based+finders

Resources