I have a model connecting to a Postgres db.
class Person < ApplicationRecord
def say_id
"#{name} has id: #{id}"
end
end
I have some attributes id,name,email as well as the method above: say_id that can be accessed via:
person = Person.new
person.id => 1
person.say_id => "John has id: 1"
I would like to have the method 'say_id' listed as an attribute as well, now when running person.attributes, I'm only seeing: id, name, email
How can I have my method included as a listable information in full, as with person.attributes but which will include my method? A usecase would be for lazily just laying out all these fields in a table of the Person-object.
In Rails 5+ you can use the attributes api to create attributes that are not backed by a database column:
class Person < ApplicationRecord
attribute :foo
end
irb(main):002:0> Person.new.attributes
=> {"id"=>nil, "email"=>nil, "name"=>nil, "created_at"=>nil, "updated_at"=>nil, "foo"=>nil}
Unlike if you used attr_accessor these actually behave very much like database backed attributes.
You can then override the getter method if you wanted to:
class Person < ApplicationRecord
attribute :foo
def foo
"foo is #{super}"
end
end
irb(main):005:0> Person.new(foo: 'bar').foo
=> "foo is bar"
But for whatever you're doing its still not the right answer. You can get a list of the methods of an class by calling .instance_methods on a class:
irb(main):007:0> Person.instance_methods(false)
=> [:foo]
Passing false filters out inherited methods.
Related
How can I handle enums in rails? I have googled this, but not finding any clean solutions. So far I have come up with including the concern below on models that use interaction_type_id. This is my foreign key to my enum table in the database. Using this approach I don't have to use ids all over my code, but when saving an object that relates to an interact_type I can say
myobject.interaction_type = :file_download
This can then persist the the database with the correct id since the concern(see concern below - included on models that use the enum) will return the correct id.
module InteractionTypeEnum
extend ActiveSupport::Concern
included do
INTERACTION_TYPE = { file_download: 1, email: 2, telesales: 3, registration: 4, enrolment: 5 }
end
def interaction_type
INTERACTION_TYPE.key(read_attribute(:interaction_type_id)).to_s.gsub('_',' ').capitalize
end
def interaction_type=(s)
write_attribute(:interaction_type_id, INTERACTION_TYPE[s])
end
end
This just feels heavy. There must be an easier/cleaner way. Now when trying to write tests for this it gets even more messy.
Most of the reasons for wanting my enums in code and database are performance (code) and reporting (database).
Any help appreciated. Thanks.
I recommend the active_enum gem.
Example from their docs, if you have an integer column called sex on the class User:
class User < ActiveRecord::Base
enumerate :sex do
value :name => 'Male'
value :name => 'Female'
end
end
Or you can define the enum in a seperate class:
class Sex < ActiveEnum::Base
value 1 => 'Male'
value 2 => 'Female'
end
class User < ActiveRecord::Base
enumerate :sex, :with => Sex
end
I like the abstraction it provides, and it saves you from having to create an entire database table just to store your enum values.
I use the following method, say I have
class PersonInfo < ActiveRecord::Base
belongs_to :person_info_type
end
and PersonInfoType is a simple domain table, containing the possible types of information.
Then I code my model as follows:
class PersonInfoType < ActiveRecord::Base
PHONE = 1
EMAIL = 2
URL = 3
end
I have a seed fills the database with the corresponding data.
And so when assigning some person-information can do something like
person.person_infos << PersonInfo.create(:info => 'http://www.some-url.com', :person_info_type_id => PersonInfoType::URL)
This code can then be further cleaned up using relations:
class PersonInfo
belongs_to :person_info_type
def self.phones
PersonInfo.where(:person_info_type_id => PersonInfoType::PHONE)
end
end
person.person_infos << PersonInfo.phones.create(:info => '555 12345')
I've been looking for a while for gems and/or plugins that implement static storage similar ActiveRecords but is not database-based. Let's call this class NonDBRecord. It should have the following property:
class Foo < NonDBRecord
add_item('a', :property1 => 'some value', :property2 => 'some more value')
add_item('b', :property1 => 'some value', :property2 => 'some more value')
end
class Bar < ActiveRecord::Base
belongs_to_nondbrecord :foo, :class_name => 'Foo'
end
# NonDBRecord declare constants automatically
[ Foo::A, Foo::B ]
# NonDBRecord is enumerable
Foo.all # returns [Foo::A,Foo::B]
# NonDBRecord is id-based
Bar.create(:foo_id => Foo::A.id)
# ...so you can search by it
x = Bar.find(:first, :conditions => { :foo_id => Foo::A.id })
# ...and is stored, retrieved, and instantiated by its id
x.foo # returns Foo::A
I've thought about simply using ActiveRecords (and database storage), but I don't feel good about it. Plus I've had to tip-toe around some eager loading problems with the ActiveRecord solution. Any help would be appreciated before I start writing my own solution.
edit
These records are meant to be enumerations. For example, let's say you're making a card game. I want to be able to do something like
class Card < NonDBRecord
attr_reader :suit, :index
end
class Game
belongs_to :wild_card, :class_name => 'Card'
end
I would say ActiveModel is what you are looking for. It comes with Rails 3 and encapsulates all kind of goodies from ActiveRecord, such as Validation, Serialization and sorts. There is a Ryan Bates railscast on that issue. Hope this helps!
As BigD says, ActiveModel is the Rails 3 way.
In Rails 2.3 I am using this as a kluge:
class TablelessModel < ActiveRecord::Base
def self.columns() #columns ||= []; end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
def save(validate = true)
validate ? valid? : true
end
end
I use that to e.g. validate contact forms that are not going to persist in any way. It's possible it could be extended for your specific purposes.
I'm just learning Ruby on Rails (no prior Ruby experience)
I have these models (not showing the migrations here for brevity - they're standard fields like firstname, city etc):
class User < ActiveRecord::Base
has_one :address
end
class Address < ActiveRecord::Base
has_one :user
end
How do I use the Address class to manage the underlying table data? Simply call methods on it? How would I pass params/attribute values to the class in that case? (since Address won't have a controller for it (since it's meant to be used internally)).
How does one go about doing something like this?
u = User.create :first_name => 'foo', :last_name => 'bar' #saves to the database, and returns the object
u.address.create :street => '122 street name' #saves to the database, with the user association set for you
#you can also just new stuff up, and save when you like
u = User.new
u.first_name = 'foo'
u.last_name ='bar'
u.save
#dynamic finders are hela-cool, you can chain stuff together however you like in the method name
u = User.find_by_first_name_and_last_name 'foo', 'bar'
#you also have some enumerable accessors
u = User.all.each {|u| puts u.first_name }
#and update works as you would expect
u = User.first
u.first_name = 'something new'
u.save
#deleting does as well
u = User.first
u.destroy
There is more to it then just this, let me know if you have any questions on stuff I didn't cover
It seems that when a child object has a reference to its parent in its setter, it fails to get initialized unless the foreign key is given first in the parameter hash.
class Bot < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :bot
def name=(text)
write_attribute(:name, "#{self.bot.name}'s #{text}")
end
end
Item.new(:name => 'pitchfork', :bot_id => 1, ... )
# => undefined method `name' for nil:NilClass (NoMethodError)
Item.new(:bot_id => 1, :name => 'pitchfork', ... )
# => #<Item id: nil, bot_id: 1, name: "r2d2's pitchfork", ... >
Note that the order of hash keys is preserved in Ruby 1.9, but the point is, bot_id must be set before the accessor that has a reference to its parent.
So, the following code works too:
item = Item.new
item.bot_id = 1
item.attributes = { :name => 'pitchfork', ... }
What's really annoying is that the build method on has_many collection doesn't work either, which I think is the right place to patch if I'd have to.
Bot.find(1).items.build(:name => 'pitchfork')
# => undefined method `name' for nil:NilClass (NoMethodError)
What's the best idea to get around this, or patch this, or am I doing anything wrong here?
You could move the string merging to an after_update callback. That way you won't have to access the Bot model until after it's properly setup.
However, I would probably keep name as a simple string and then add a virtual attribute for the merged string. That way it's also updated if the name of Bot is changed.
class Item < ActiveRecord::Base
belongs_to :bot
def full_name
#full_name ||= "#{bot.name}'s #{name}"
end
end
I have the following classes:
Project
Person
Person > Developer
Person > Manager
In the Project model I have added the following statements:
has_and_belongs_to_many :people
accepts_nested_attributes_for :people
And of course the appropriate statements in the class Person. How can I add a Developer to a Project through the nested_attributes method? The following does not work:
#p.people_attributes = [{:name => "Epic Beard Man", :type => "Developer"}]
#p.people
=> [#<Person id: nil, name: "Epic Beard Man", type: nil>]
As you can see the type attributes is set to nil instead of "Developer".
Solution for Rails3: attributes_protected_by_default in now a class-method:
class Person < ActiveRecord::Base
private
def self.attributes_protected_by_default
super - [inheritance_column]
end
end
I encountered a similar problem few days ago. The inheritance column(i.e. type) in a STI model is a protected attribute. Do the following to override the default protection in your Person class.
Rails 2.3
class Person < ActiveRecord::Base
private
def attributes_protected_by_default
super - [self.class.inheritance_column]
end
end
Rails 3
Refer to the solution suggested by #tokland.
Caveat:
You are overriding the system protected attribute.
Reference:
SO Question on the topic
Patches above did not work for me, but this did (Rails3):
class ActiveRecord::Reflection::AssociationReflection
def build_association(*options)
if options.first.is_a?(Hash) and options.first[:type].presence
options.first[:type].to_s.constantize.new(*options)
else
klass.new(*options)
end
end
end
Foo.bars.build(:type=>'Baz').class == Baz
For those of us using Mongoid, you will need to make the _type field accessible:
class Person
include Mongoid::Document
attr_accessible :_type
end