So I have a User model that has a first_name and last_name column.
I would like to create a new virtual attribute called username, that I can access even in my routes file.
I tried doing this:
attr_accessor :username
# getter
def username
#username
end
# setter
def username=(user)
#username = "#{user.first_name}.#{user.last_name}"
end
But when I try to set it, I get a wrong number of arguments error.
[27] pry(main)> u.username=(u)
ArgumentError: wrong number of arguments (1 for 0)
from
[28] pry(main)> u.username = u
ArgumentError: wrong number of arguments (1 for 0)
from
u is a valid User record.
Ideally, I would like to do two things. I would like to be able to check the user.username for all User objects in my DB and it should return per the above setter method.
How can I achieve this?
I think, you have a problem somewhere else. It's not easy to find out what's going wrong with your code without seeing it, but this code itself works without any errors:
class User < ActiveRecord::Base
def username=(value)
#username = value
end
def username
#username
end
end
user = User.new(:first_name => "Bill", :last_name => "Gates")
user.username = "billgates"
user.username
Or you can use attr_accessor to replace manual definition of getter and setter:
class User < ActiveRecord::Base
attr_accessor :username
end
Take a look on this screencast to clarify things: http://railscasts.com/episodes/16-virtual-attributes
Related
I'm working in a large Rails 2.3 application and I have data on a model that would like to move to another model. I need to do this is phases as there are places in the Rails code base that are reading and writing this model data and outside applications reading the table data directly via SQL. I need to allow a period of time where the attribute is synchronized on both models and their associated tables before I drop one model and table altogether.
My models have a has_one and belongs_to relationship like this:
class User < ActiveRecord::Base
has_one :user_email, :inverse_of => :user
accepts_nested_attributes_for :user_email
validates_presence_of :email
def email=( value )
write_attribute(:email, value)
user_email.write_attribute(:email, value)
end
end
class UserEmail < ActiveRecord::Base
belongs_to :user, :inverse_of => :user_email
validates_presence_of :email
def email=( value )
write_attribute(:email, value)
user.write_attribute(:email, value)
end
end
I'd like to do away with UserEmail and its associated table altogether, but for a time I need to keep email up-to-date on both models so if it's set on one model, it's changed on the other. Overriding email= on each model is straightforward, but coming up with a commit strategy is where I'm hitting a wall.
I have places in the code base that are doing things like:
user.user_email.save!
and I'm hoping to find a way to continue to allow this kind of code for the time being.
I can't figure out a way to ensure that saving an instance of User ensures the corresponding UserEmail data is committed and saving an instance of UserEmail ensures the corresponding User instance data is also committed without creating an infinite save loop in the call backs.
This is the flow I would like to be able to support for the time being:
params = { user: { email: 'foo#bar.com', user_email: { email: 'foo#bar.com' } } }
user = User.create( params )
user.email = "moo#bar.com"
user.save
puts user.user_email # puts "moo#bar.com"
user.user_email.email = "foo#bar.com"
user.user_email.save
user.reload
puts user.email # puts "foo#bar.com"
Is there a way to achieve this sort of synchronization between the User and UserEmail models so they are kept in sync?
If it helps, I can probably do away with accepts_nested_attributes_for :user_email on User.
Using ActiveModel::Dirty
In User model
after_save :sync_email, :if => :email_changed?
def sync_email
user_email.update_column(:email, email) if user_email.email != email
end
In UserEmail model
after_save :sync_email, :if => :email_changed?
def sync_email
user.update_column(:email, email) if user.email != email
end
Let's assume, for sanity's sake, that the models are "User" and "Cart", and the shared field is "email". I would do this:
#in User
after_save :update_cart_email
def update_cart_email
if self.changes["email"]
cart = self.cart
if cart.email != self.email
cart.update_attributes(:email => self.email)
end
end
end
#in Cart
after_save :update_user_email
def update_user_email
if self.changes["email"]
user = self.user
if user.email != self.email
user.update_attributes(:email => user.email)
end
end
end
Because we check if the other model's email has already been set, it shouldn't get stuck in a loop.
This works if you drop accepts_nested_attributes_for :user_email -- otherwise you'll get a save loop that never ends.
I'm trying to do in a rails console
>> user = User.new(:name => "", :email => "test#example.com")
=> #<User not initialized>
My User class looks like
class User < ActiveRecord::Base
attr_accessor :name, :email
has_many :microposts
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
I am following along from the rails tutorial. Why am I not able to initialize the object ?
tl;dr: Copy exactly from the book and you should be fine. (Note: I am the author.)
The example in question is from Chapter 4 of the Ruby on Rails Tutorial book and is not an Active Record model. In particular, the User class shown in the question is based on Listing 4.9:
class User
attr_accessor :name, :email
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
This class does not inherit from ActiveRecord::Base, but rather must be included explicitly in the console using require './example_user.rb', as described in Section 4.4.5. The behavior you're seeing is the result of including < ActiveRecord::Base in the first line, but if you copy the code in Listing 4.9 exactly you should see the expected behavior.
are you running your console in the same file directory as your project? I'd also try switching up theĀ notation to the example used in the book and see if that gets you anywhere.
you can also try calling User.new with no attributes and see if it generates an object as listed in 6.1.3 of the tutorial , and then fill in the attributes and see if it works.
also make sure you dont have a validation on your user name in your model.
and a last check you can run user.error to see why it might not be saving
First, I assume that User model persists in your Rails app. That means, that you already have a migrated User model before running rails console.
If that table doesn't exist, you will be instanly prompted with:
=> User(Table doesn't exist)
Now, let's have some fun in rails console:
First things first, don't override initialize method in Rails model; While creating an object initialize method from ActiveRecord takes precedence (I think), so it may create conflicts. Instead use after_initialize callback. In console:
class User < ActiveRecord::Base
attr_accessible :name, :email
def after_initialize(attributes = {})
self[:name] = attributes[:name]
self[:email] = attributes[:email]
end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Now,
u = User.new({name: "Foo", email: "foo#bar.org"})
#<User name: "Foo", email: "foo#bar.org", created_at:nil updated_at: nil>
u.formatted_email
#=> "Foo <foo#bar.org>"
All done! Sweet.
UPDATE:
As per your recent gist; I see no point of having after_initialize at all. Rails does that on it's own.
First thing first, replace attr_accessor with attr_accessbile.
attr_accessor is ruby method(courtesy, metaprogramming) which creates getter and setter for provided instance variable. Rails uses attr_accessible for that; for security concerns, only instance variables allowed in attr_accessible allowed for mass-assignment (by sending params hash).
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email
#def after_initialize(attributes = {})
# self[:name] = attributes[:name]
# self[:email] = attributes[:email]
#end
def formatted_email
"#{self.name} <#{self.email}>"
end
end
Are you running console using the rails c command to load your environment from the root directory of your project? Typing irb to start a console session does not load the Rails application environment by itself.
Here are some more troubleshooting tips
Check to make sure that the development database specified in config/database.yml is running
Check to make sure a migration exists to create the Users table
Check to make sure the migrations have run with rake db:migrate
Check to make sure that a Users table actually does exist in the database, with columns of type varchar (or text) for fields :name and :email
I'd like to have a number of dynamic attributes for a User model, e.g., phone, address, zipcode, etc., but I would not like to add each to the database. Therefore I created a separate table called UserDetails for key-value pairs and a belongs_to :User.
Is there a way to somehow do something dynamic like this user.phone = "888 888 8888" which would essentially call a function that does:
UserDetail.create(:user => user, :key => "phone", :val => "888 888 8888")
and then have a matching getter:
def phone
UserDetail.find_by_user_id_and_key(user,key).val
end
All of this but for a number of attributes provided like phone, zip, address, etc., without arbitrarily adding a ton of of getters and setters?
You want to use the delegate command:
class User < ActiveRecord:Base
has_one :user_detail
delegate :phone, :other, :to => :user_detail
end
Then you can freely do user.phone = '888 888 888' or consult it like user.phone. Rails will automatically generate all the getters, setters and dynamic methods for you
You could use some meta-programming to set the properties on the model, something like the following: (this code was not tested)
class User < ActiveRecord:Base
define_property "phone"
define_property "other"
#etc, you get the idea
def self.define_property(name)
define_method(name.to_sym) do
UserDetail.find_by_user_id_and_key(id,name).val
end
define_method("#{name}=".to_sym) do |value|
existing_property = UserDetail.find_by_user_id_and_key(id,name)
if(existing_property)
existing_property.val = value
existing_property.save
else
new_prop = UserDetail.new
new_prop.user_id = id
new_prop.key = name
new_prop.val = value
new_prop.save
end
end
end
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
I have a model called Profile which is belong_to User, so there is 'user_id' for the database to keep track of. In the local admin interface I made for this model I wanted to provide the flexibility of allowing admin to enter an username to a field in the editing screen, and then resolve that to user_id for saving in controller.
However the question is, how do I check against that the username have a valid return? I found that in ActiveRecord::Validation there is no method for validating the existence of the association. How will you handle a situation like this?
Update: What I want to do is to validate that the username field in the form is indeed a real user, then I could save that user_id back to the profile admin is editing. Here 'return' means the user object returned.
This problem is a good candidate for virtual attributes.
Instead of trying to resolve the username, let the profile model to the job for you.
class Profile
belongs_to :user
# ...
def username
user.try(:username)
end
def username=(value)
self.user = User.find_by_username(value)
end
end
Then in your form
<% form_for #profile do |f| %>
<%= f.text_field :username %>
<% end %>
When submitted, the value for the username field is automatically passed with all the other real activerecord attributes. ActiveRecord will look for the username= setter and will resolve the association.
If the association returns nil (no record exists with given username), then it will set current user_id to nil and validation will fail as expected.
You might want to customize the error code to make more meaningful.
EDIT: Added example.
validate :ensure_username_exists
def username=(value)
self.user_id = User.find_by_username(value) || 0
end
protected
def ensure_username_exists
if user_id == 0 # nil is allowed
errors.add(:user_id, "Username doesn't exists")
return false
end
end
This is a useful reference for Active Record Associations: http://guides.rubyonrails.org/association_basics.html
To check for the existence of the association, just check association.nil?
if #profile.user.nil?
... something ...
end
To check if the username has a valid return, well I'm not quite sure what you mean. Could you expand on that?