Getting types of the attributes in an ActiveRecord object
Asked Answered
F

8

81

I would like to know if it is possible to get the types (as known by AR - eg in the migration script and database) programmatically (I know the data exists in there somewhere).

For example, I can deal with all the attribute names:

ar.attribute_names.each { |name| puts name }

.attributes just returns a mapping of the names to their current values (eg no type info if the field isn't set).

Some places I have seen it with the type information:

in script/console, type the name of an AR entity:

>> Driver
=> Driver(id: integer, name: string, created_at: datetime, updated_at: datetime)

So clearly it knows the types. Also, there is .column_for_attribute, which takes an attr name and returns a column object - which has the type buried in the underlying database column object, but it doesn't appear to be a clean way to get it.

I would also be interested in if there is a way that is friendly for the new "ActiveModel" that is coming (rails3) and is decoupled from database specifics (but perhaps type info will not be part of it, I can't seem to find out if it is).

Thanks.

Familist answered 8/3, 2010 at 5:57 Comment(0)
C
117

In Rails 3, for your model "Driver", you want Driver.columns_hash.

Driver.columns_hash["name"].type  #returns :string

If you want to iterate through them, you'd do something like this:

Driver.columns_hash.each {|k,v| puts "#{k} => #{v.type}"}

which will output the following:

id => integer
name => string
created_at => datetime
updated_at => datetime
Carnival answered 27/3, 2013 at 18:49 Comment(2)
Do you know how can I test if a value match with a column, something like this 2.is_a? Driver.columns_hash["name"].typeBrobdingnagian
This does not work with virtual attributes in rails 5+Maurine
L
41

In Rails 5, you can do this independently of the Database. That's important if you use the new Attributes API to define (additional) attributes.

Getting all attributes from a model class:

pry> User.attribute_names
=> ["id",
 "firstname",
 "lastname",
 "created_at",
 "updated_at",
 "email",...

Getting the type:

pry> User.type_for_attribute('email')
=> #<ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlString:0x007ffbab107698
 @limit=255,
 @precision=nil,
 @scale=nil>

That's sometimes more information than needed. There's a convenience function that maps all these types down to a core set (:integer, :string etc.)

> User.type_for_attribute('email').type
=> :string 

You can also get all that data in one call with attribute_types which returns a 'name': type hash.

Laterite answered 24/2, 2016 at 2:59 Comment(3)
This works in Rails 4.2 too. It's important to note that type_for_attribute('id') only takes a string. It's a bit confusing because type_for_attribute('id').type returns a symbol.Crystlecs
If by Database independent you meant the ORM, then Mongoid hasn't implemented this type_for_attribute yet. Maybe after Rails 5 has an official release.Gypsophila
In Rails 7 with ActiveRecord Encryption use type_for_attribute(:attribute_name).cast_type.type instead. cast_type is required to read a custom type set with a call like attribute :attribute_name, :boolean.Misconceive
D
23

You can access the types of the columns by doing this:

#script/console
Driver.columns.each {|c| puts c.type}

If you want to get a list of all column types in a particular Model, you could do:

Driver.columns.map(&:type) #gets them all
Driver.columns.map(&:type).uniq #gets the unique ones
Dagger answered 8/3, 2010 at 6:14 Comment(1)
Thank you. I am guessing this won't be part of ActiveModel though as it is bound to columns (which probably won't make it to AM). But that works just awesome for AR. You know I actually knew that at the back of my mind, but for some reason blocked it out.Familist
R
9

In rails 5 this will give you a list of all field names along with their data type:

Model_Name.attribute_names.each do |k| puts "#{k} = #{Model_Name.type_for_attribute(k).type}" end
Replete answered 12/12, 2016 at 13:36 Comment(1)
Note type_for_attribute expects strings. It happily takes symbols, or non-existant names, returning a ActiveModel::Type::Value that does no casting! IMO a bug, I'll try to report/fix.Shoifet
M
7

Rails 5+ (works with virtual attributes as well):

Model.attribute_types['some_attribute'].type
Maurine answered 16/10, 2019 at 17:47 Comment(0)
C
5

This snippet will give you all the attributes of a model with the associated database data types in a hash. Just replace Post with your Active Record Model.

Post.attribute_names.map {|n| [n.to_sym,Post.type_for_attribute(n).type]}.to_h

Will return a hash like this.

=> {:id=>:integer, :title=>:string, :body=>:text, :created_at=>:datetime, :updated_at=>:datetime, :topic_id=>:integer, :user_id=>:integer} 
Crystlecs answered 25/2, 2016 at 18:43 Comment(0)
L
2

Assuming Foobar is your Active Record model. You can also do:

attributes = Foobar.attribute_names.each_with_object({}) do |attribute_name, hash|
  hash[attribute_name.to_sym] = Foobar.type_for_attribute(attribute_name).type
end

Works on Rails 4 too

Loadstone answered 8/9, 2021 at 5:59 Comment(0)
H
0

In Rails 4 You would use Model.column_types.

Harriman answered 14/5, 2020 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.