I have the following code in an Active Record file.
class Profile < ActiveRecord::Base
attr_accessor :tmp_postal_code
def postal_code=(postal_code)
#temp_postal_code = postal_code[:first] + "-" + postal_code[:second]
end
def postal_code
#temp_postal_code
end
end
I first overwrote postal_code=(postal_code) because params in a controller is a hash e.g., ({:first => "1234", :second => "9999"}). Second, when I tried use a getter method, I got nil, so I added the getter method. In order to share the value of the postal_code attribute, I made #temp_postal_code.
Now everything works except for one. Look at the console output below.
>> p = Profile.new
SQL (0.1ms) SET NAMES 'utf8'
SQL (0.1ms) SET SQL_AUTO_IS_NULL=0
Profile Columns (1.3ms) SHOW FIELDS FROM `profiles`
=> #<Profile id: nil, name: nil, pr: "", age: nil, postal_code: nil>
>> p.postal_code = {:first => "123", :second => "9999"}
=> {:second=>"9999", :first=>"123"}
>> p.postal_code
=> "123-9999"
>> p.postal_code
=> "123-9999"
>> p.name = "TK"
=> "TK"
>> p.postal_code
=> "123-9999"
>> p.pr = "Hello"
=> "Hello"
>> p.age = 20
=> 20
>> p
=> #<Profile id: nil, name: "TK", pr: "Hello", age: 20, postal_code: nil> # WHY!!!
>> p.postal_code
=> "123-9999"
When I try to access postal_code attribute individually by p.postal_code, the value exists. But when I try to show p, postal_code is nil. It looks like the latter is used for save operation. I cannot save the postal code with a meaningful value.
Something is wrong with my understanding with virtual attributes and overwriting of attributes. By further exploration, I noticed the difference between hash notation and dot notation.
>> p[:postal_code]
=> nil
>> p[:name]
=> "TK"
>> p.postal_code
=> "123-9999"
I have no idea why this occurs. I want to be able to save with a postal_code filled in.
You want to use the super method so that it actually gets put into the AR attributes.
def postal_code=(postal_code)
super(postal_code[:first] + "-" + postal_code[:second])
end
# you shouldn't even need this anymore
def postal_code
#temp_postal_code
end
You wont need the attr_accessor anymore either. Hope that helps.
Related
I have on my Message model, an after_create which creates a new instance of a Notification like such.
after_create :send_notification
def send_notification
n = Notification.new :name => "#{self.sender.smart_name} sent you a message:", :user_id => self.receiver_id, :notification_type => 'message', :subject => self.subject
n.save
end
However, the objects that are created all have their created_at and updated_at set to nil.
#<Notification:0x0000000c486208
id: 123123,
user_id: 3423,
name: "I sent you a message:\n" + "10:27",
notification_type: "message",
created_at: nil,
updated_at: nil>
I've checked to see that the model.record_timestamps is set to true based on this answer.
I don't have anything set on active_record as suggested here.
I'm using Mysql on Rails 4.
You should call n.reload after n.save just to get the timestamps read after save
I'm not sure if I'm even asking the right question. I may be approaching the problem incorrectly, but basically I have this situation here:
obj = get_user(params)
obj.profile => {:name => "John D", :age => 40, :sex => "male"} #Has to be of class Hash
obj.profile.name => "John D"
obj.profile[:name] => "John D"
obj.profile.job => nil
So basically, I have to satisfy all of these conditions and I'm not sure exactly how to even approach this (I just learned Ruby today).
Note the dot notation for accessing the inner variables, otherwise I would have just had profile be a hash of symbols. So I've tried two methods, which only sort of get me there
Method 1: Make profile an OpenStruct
So this allows me to access name, age and sex using the dot notation, and it automatically returns nil if a key doesn't exist, however obj.profile is of the type OpenStruct instead of Hash
Method 2: Make profile its own class
With this I set them as instance variables, and I can use method_missing to return nil if they don't exist. But, I again run into the issue of obj.profile not being the correct type/class
Is there something I'm missing? Is there a way to maybe differentiate between
obj.profile
obj.profile.name
in the getter function and return either a hash or otherwise?
Can I change what is returned by my custom class for profile, so it returns a Hash instead?
I've even tried checking the args and **kwargs in the get function for obj.profile and neither of them seem to help, or populate if I call obj.profile.something
If it absolutely has to be a Hash:
require 'pp'
module JSHash
refine Hash do
def method_missing(name, *args, &block)
if !args.empty? || block
super(name, *args, &block)
else
self[name]
end
end
end
end
using JSHash
profile = {:name => "John D", :age => 40, :sex => "male"}
pp profile.name # "John D"
pp profile[:name] # "John D"
pp profile.job # nil
pp profile.class # Hash
But still better not to be a Hash, unless it absolutely needs to:
require 'pp'
class Profile < Hash
def initialize(hash)
self.merge!(hash)
end
def method_missing(name, *args, &block)
if !args.empty? || block
super(name, *args, &block)
else
self[name]
end
end
end
profile = Profile.new({:name => "John D", :age => 40, :sex => "male"})
pp profile.name
pp profile[:name]
pp profile.job
For only a few hash keys, you can easily define singleton methods like so:
def define_getters(hash)
hash.instance_eval do
def name
get_val(__method__)
end
def job
get_val(__method__)
end
def get_val(key)
self[key.to_sym]
end
end
end
profile = person.profile #=> {name: "John Doe", age: 40, gender: "M"}
define_getters(profile)
person.profile.name #=> "John Doe"
person.profile.job #=> nil
Reflects changed values as well (in case you were wondering):
person.profile[:name] = "Ralph Lauren"
person.profile.name #=> "Ralph Lauren"
With this approach, you won't have to override method_missing, create new classes inheriting from Hash, or monkey-patch the Hash class.
However, to be able to access unknown keys through method-calls and return nil instead of errors, you'll have to involve method_missing.
This Hash override will accomplish what you're trying to do. All you need to do is include it with one of your class files that you're already loading.
class Hash
def method_missing(*args)
if args.size == 1
self[args[0].to_sym]
else
self[args[0][0..-2].to_sym] = args[1] # last char is chopped because the equal sign is included in the string, print out args[0] to see for yourself
end
end
end
See the following IRB output to confirm:
1.9.3-p194 :001 > test_hash = {test: "testing"}
=> {:test=>"testing"}
1.9.3-p194 :002 > test_hash.test
=> "testing"
1.9.3-p194 :003 > test_hash[:test]
=> "testing"
1.9.3-p194 :004 > test_hash.should_return_nil
=> nil
1.9.3-p194 :005 > test_hash.test = "hello"
=> "hello"
1.9.3-p194 :006 > test_hash[:test]
=> "hello"
1.9.3-p194 :007 > test_hash[:test] = "success"
=> "success"
1.9.3-p194 :008 > test_hash.test
=> "success"
1.9.3-p194 :009 > test_hash.some_new_key = "some value"
=> "some value"
1.9.3-p194 :011 > test_hash[:some_new_key]
=> "some value"
How do I clone a single attribute in a Rails model? This didn't work:
irb(main):309:0> u.reload
=> #<User id: 1, username: "starrychloe", ...
irb(main):310:0> u2 = u.dup
=> #<User id: nil, username: "starrychloe", ...
irb(main):311:0> u2 = u.clone
=> #<User id: 1, username: "starrychloe", ...
irb(main):312:0> u2.username = u.username.clone
=> "starrychloe"
irb(main):313:0> u2.username = 'star'
=> "star"
irb(main):314:0> u.username ############ Changes original
=> "star"
Neither did this:
irb(main):320:0> u.reload
=> #<User id: 1, username: "starrychloe", ...
irb(main):321:0> u2 = u.clone
=> #<User id: 1, username: "starrychloe", ...
irb(main):322:0> u2[:username] = u[:username].clone
=> "starrychloe"
irb(main):323:0> u2.username = 'cow'
=> "cow"
irb(main):324:0> u.username ############ Changes original
=> "cow"
#dup doesn't copy the ID, and #clone on the attribute keeps the reference to the same string. This will not solve my problem.
u2 = User.new(u.attributes.merge(username: "cow"))
Also, take a look at this question. It has a lot of interesting info on similar subject:
What is the easiest way to duplicate an activerecord record?
Do you want to duplicate an instance or an attribute?
To duplicate an instance, use u2 = u.dup not u2 = u.clone.
You might wanna look into amoeba gem. https://github.com/rocksolidwebdesign/amoeba
To make a copy of the instance with its attributes and de-reference you can do this:
u2 = u.class.new(u.attributes)
I ended up making copies of each of the fields I wanted to keep track of:
#oldUsername = #user.username.clone
User.new looked promising, but it treated the copy as a new object, when it was an existing model, and output invalid forms to edit the model in the views:
> app.controller.view_context.form_for u2 do end # This is from Rails console
=> "<form accept-charset=\"UTF-8\" action=\"/users\" class=\"new_user\" id=\"new_user_1\" method=\"post\">
So it would attempt to PATCH to /users (from the view), which is invalid, when it should PATCH to /users/1/.
It's unbelievable that Rails won't clone objects correctly. In Java, you could use u2.setProperty( u.getProperty().clone() ) and be sure to have a new object that won't interfere with the old one.
>> Reply.first
=> #< Reply id: 1, body: "line1\r\n\r\nline2\r\n" >
But when I do
>> Reply.first.body
=> "line1"
Its breaking a few of my tests where they are looking for :
assert_difference 'Reply.where(:body => "line1\r\n\r\nline2").count' do
How can my tests be reassured there are line breaks?
Seems like you have a custom getter, something like:
class Reply < ActiveRecord::Base
def body
"foo"
end
end
reply = Reply.new(body: "bar")
#=> #<Reply id:nil, body: "bar" created_at: nil, updated_at: nil>
reply.body
#=> "foo"
In that case, you can fetch the raw attribute using Model[:attribute_name]:
reply[:body]
#=> "bar"
Change the snytax a little bit when you have backslash's
assert_difference 'Reply.where("body = 'line1\r\n\r\nline2\r\n'").count' do
Something strange is happening in Rails 2.3.14. Any ideas why the following happens?
Example
Suppose we have classes like
class Article < ActiveRecord::Base
has_many :prices
end
class Price < ActiveRecord::Base
end
Then in a irb session the following happens.
>> a = Article.first
=> #<Article id: 980190962>
>> a.prices.first.object_id
=> 97498070
>> a.prices.first.object_id
=> 97470500
>> a.prices.first.object_id
=> 97451010
>> a.valid?
=> true
>> a.prices.first.object_id
=> 97374790
>> a.prices.first.object_id
=> 97374790
So at first the object_id changes each time the record is accessed (yes, it's always the same one). Later after a call to #valid? this behavior stops. Instead everything is fine. You get the same object on each call.
Why is this important?
Let's assume you add a validation to Price
class Price < ActiveRecord::Base
validates_presence_of :amount
end
Then you want to change a price of an article.
>> a = Article.first
=> #<Article id: 980190962>
>> p = a.prices.first
=> #<Price id: 280438907, amount: 1.0, article_id: 980190962>
>> p.amount = nil # oops, accidentally we assigned nil
=> nil
>> p.valid?
=> false
>> a.valid?
=> true
What's that? The price is invalid, but the article is not? This shouldn't have happend, because by default the :validate-option of has_many is set to true. This happens because of the changing object_ids
Every new context(request/response) will regenerate the object IDs. As you are running the query a.prices.first.object_id it will hit the query to get the first price every time and as each hit will generate new object IDs.
But in second case you have extracted first price into a instance variable and working on that so no new request/response is going to database hence it is giving same object IDS.