How to use model without a controller in Ruby on Rails? - ruby-on-rails

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

Related

Rails make model methods appear as attributes

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.

How do I set a virtual attribute on an existing model?

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

Keeping the same attribute in sync on two models in a Rails 2.x application?

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.

setting muliple columns with same value , in a factory girl

Factory.define(:player) do |u|
u.association(:owner), :factory => :user
u.association(:updater), :factory => user
end
Can i rewrite the above definition such that , I can initialize the values of the owner and updater to be the same, without passing them in explicitly when i call create
Factory.define(:player) do |uu|
uu.association(:owner), :factory => :user
uu.association(:updater), { |player| player.owner }
end
When defining associations, I often find it easier to use one of the after_create or after_build hooks:
Factory.define(:player) do |u|
after_build do |player|
user = FactoryGirl.create :user
player.owner = user
player.creator = user
end
end
I also usually try to set up my factories so they'll work whether I'm building (instantiating) or creating (instantiating and saving), but ActiveRecord is a bit finicky about how you set up the associations when you're just building, so I used create in this example.

rails dynamic attributes

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

Resources