Rails console is not showing attribute when called - ruby-on-rails

>> 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

Related

Rails: #inspect not displaying attribute

I am defining #foo as a class instance attribute, and using the after_initialize callback to set the value of this when a record is created/loaded:
class Blog < ActiveRecord::Base
#foo = nil
after_initialize :assign_value
def assign_value
#foo = 'bar'
end
end
However, when I inspect a Blog object, I am not seeing the #foo attribute:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>"
What do I need to do to get inspect to include this? Or conversely, how does inspect determine what to output?
Thanks.
Active record determines which attributes to show in inspect based on the columns in the database table:
def inspect
attributes_as_nice_string = self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
end
}.compact.join(", ")
"#<#{self.class} #{attributes_as_nice_string}>"
end
Lifted from base.rb on github
To change the output of inspect you'll have to overwrite it with your own method e.g.
def inspect
"#{super}, #foo = #{#foo}"
end
Which should output:
> Blog.first.inspect
=> "#<Blog id: 1, title: 'Test', created_at: nil, updated_at: nil>, #foo = 'bar'"

rails Model.create(:attr=>"value") returns model with uninitialized fields

This is really stumping me. The process works fine if I go about it with #new and then #save, but #create returns a model instance with all the fields set to nil.
e.g:
Unexpected behavior:
ruby-1.9.2-p0 > EmailDefault.create(:description=>"hi")
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
Expected behaviour:
ruby-1.9.2-p0 > e = EmailDefault.new
=> #<EmailDefault id: nil, description: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p0 > e.description = "hi"
=> "hi"
ruby-1.9.2-p0 > e.save
=> true
ruby-1.9.2-p0 > EmailDefault.last
=> #<EmailDefault id: 4, description: "hi", created_at: "2011-02-27 22:25:33", updated_at: "2011-02-27 22:25:33">
What am I doing wrong?
--update--
Turns out I was mis-using attr_accessor. I wanted to add some non-database attributes, so I did it with:
attr_accessible :example_to, :cc_comments
which is wrong, and caused the situation #Heikki mentioned. What I need to do is:
attr_accessor :example_to, :cc_comments
You need to white list those properties with attr_accessible to enable mass-assignment.
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html#method-i-attr_accessible
--edit
By default all attributes are available for mass-assignment. If attr_accessible is used then mass-assignment will work only for those attributes. Attr_protected works the opposite way ie. those attributes will be protected from mass-assignment. Only one should be used at a time. I prefer the white listing with attr_accessible.

after_initialize virtual attribute

I am unable to assign a value to a virtual attribute in the after_initialize method:
attr_accessor :hello_world
def after_initialize
self[:hello_world] = "hello world"
end
In my view file:
hello world defaulted? <%= #mymodel.hello_world %>
This doesn't return any output.
Do you have any suggestions or any alternative to set up a default value for a virtual attribute?
You are using an odd method of assignation within your after_initialize callback. You just need to assign to self.hello_world or even #hello_world. Your assignment has created a hash within your instance of the class with a key of :hello_world and the value as expected. Within your view you could reference #mymodel[:hello_world] but this is far from idiomatic.
The following example model and console session shows the effect of using various methods of initializing the virtual attribute.
class Blog < ActiveRecord::Base
attr_accessor :hello_world1, :hello_world2, :hello_world3
def after_initialize
self.hello_world1 = "Hello World 1"
self[:hello_world2] = "Hello World 2"
#hello_world3 = "Hello World 3"
end
end
ruby-1.9.2-p0 > b=Blog.new
=> #<Blog id: nil, title: nil, content: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p0 > b.hello_world1
=> "Hello World 1"
ruby-1.9.2-p0 > b.hello_world3
=> "Hello World 3"
ruby-1.9.2-p0 > b.hello_world2
=> nil
ruby-1.9.2-p0 > b[:hello_world2]
=> "Hello World 2"
ruby-1.9.2-p0 > b[:hello_world1]
=> nil

Issue with setter override on ActiveRecord

This is not exactly a question, it's rather a report on how I solved an issue with write_attribute when the attribute is an object, on Rails' Active Record. I hope this can be useful to others facing the same problem.
Let me explain with an example. Suppose you have two classes, Book and Author:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Very simple. But, for whatever reason, you need to override the author= method on Book. As I'm new to Rails, I've followed the Sam Ruby's suggestion on Agile Web Development with Rails: use attribute_writer private method. So, my first try was:
class Book < ActiveRecord::Base
belongs_to :author
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.write_attribute(:author, author)
end
end
Unfortunately, this does not work. That's what I get from console:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"
=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> nil
It seems that Rails does not recognize it is an object and makes nothing: after the attribuition, author is still nil! Of course, I could try write_attribute(:author_id, author.id), but it does not help when the author is not saved yet (it still has no id!) and I need the objects be saved together (author must be saved only if book is valid).
After search a lot for a solution (and try many other things in vain), I found this message: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8, so finally I could had some working code:
class Book < ActiveRecord::Base
belongs_to :author
def author_with_lookup=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
self.author_without_lookup = author
end
alias_method_chain :author=, :lookup
end
This time, the console was nice to me:
>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author = "Lewis Carroll"=> "Lewis Carroll"
>> book
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> #<Author id: nil, name: "Lewis Carroll", created_at: nil, updated_at: nil>
The trick here is the alias_method_chain, that creates an interceptor (in this case author_with_lookup) and an alternative name to the old setter (author_without_lookup). I confess it took some time to understand this arrangement and I'd be glad if someone care to explain it in detail, but what surprised me was the lack of information about this kind of problem. I have to google a lot to find just one post, that by the title seemed initially unrelated to the problem. I'm new to Rails, so what do you think guys: is this a bad practice?
I recommend creating a virtual attribute instead of overriding the author= method.
class Book < ActiveRecord::Base
belongs_to :author
def author_name=(author_name)
self.author = Author.find_or_initialize_by_name(author_name)
end
def author_name
author.name if author
end
end
Then you could do cool things like apply it to a form field.
<%= f.text_field :author_name %>
Would this work for your situation?
When you override the accessor, you have to set an actual DB attribute for write_attribute and self[:the_attribute]=, and not the name of the association-generated attribute you're overriding. This works for me.
require 'rubygems'
require 'active_record'
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
ActiveRecord::Schema.define do
create_table(:books) {|t| t.string :title }
create_table(:authors) {|t| t.string :name }
end
class Book < ActiveRecord::Base
belongs_to :author
def author=(author_name)
found_author = Author.find_by_name(author_name)
if found_author
self[:author_id] = found_author.id
else
build_author(:name => author_name)
end
end
end
class Author < ActiveRecord::Base
end
Author.create!(:name => "John Doe")
Author.create!(:name => "Tolkien")
b1 = Book.new(:author => "John Doe")
p b1.author
# => #<Author id: 1, name: "John Doe">
b2 = Book.new(:author => "Noone")
p b2.author
# => #<Author id: nil, name: "Noone">
b2.save
p b2.author
# => #<Author id: 3, name: "Noone">
I strongly recommend doing what Ryan Bates suggests, though; create a new author_name attribute and leave the association generated methods as they are. Less fuzz, less confusion.
I solved this problem using alias_method
class Book < ActiveRecord::Base
belongs_to :author
alias_method :set_author, :author=
def author=(author)
author = Author.find_or_initialize_by_name(author) if author.is_a? String
set_author(author)
end
end

ActiveRecord Attributes

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.

Resources