I'm trying to create a callback for a Post model. The callback should update the slug post attribute. This is the relevant code in post.rb:
class Post < ApplicationRecord
after_create :set_slug
SLUG_FILTER = '/\!?##$`\'%^&*+=",.()[]{}‘’'
private
def set_slug
self.slug = title.delete(SLUG_FILTER).strip.gsub(/\s+/,'-').downcase
end
end
When I create a Post in rails console I get this output:
irb> author(1).posts.create!(title: 'some post title', markdown: 'test **123**')
=> #<Post id: 25, title: "some post title", markdown: "test **123**", ..., slug: "some-post-title">
irb> Post.find(25)
=> #<Post id: 25, title: "some post title", markdown: "test **123**", ..., slug: nil>
It seems that the slug attribute is created but then it becomes nil. Why is the slug attribute nil and not some-post-title? I'd appreciate any help.
self.slug = is just assign slug variable so not save to database.
So you should like the below code.
before_create :set_slug
This code assign to slug before writing to database.
the after_create callback is trigger after the object was saved. If you modify an attribute it will not be save again.
Before_create should work :)
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 am manually creating objects in the rails console using Model.new(<attributes here>). Is there an easy way to list out which attributes a model will require me to include in order for the .save call to succeed?
I am running rails 4.2.3
You can get an array of validators using Model.validators. You'll have to parse this in some way to extract those validations for presence, something like:
presence_validated_attributes = Model.validators.map do |validator|
validator.attributes if validator.is_a?(ActiveRecord::Validations::PresenceValidator)
end.compact.flatten
I found a simpler way to accomplish the same thing:
When you do a failed create you can check the error message on the object.
# app/models/price.rb
class Price < ActiveRecord::Base
validates_presence_of :value
end
# in console
p = Price.new()
=> #<Price id: nil, created_at: nil, updated_at: nil, value: nil>
p.save
=> false
p.errors.messages
=> {:value=>["can't be blank"]}
In case you the mandatory attributes with error messages
book = Book.new
book.valid?
book.errors.messages
In case you just want the name of attributes without an error message
book = Book.new
book.valid?
book.errors.messages.keys
I have a model Messages, for which I have a recipient_list which saves as a string. For whatever reason on save, all of my parameters other than the recipient_list are being saved, with only the recipient_list being left out. I'm stumped as to what the cause for this may be.
Model:
class Message < ActiveRecord::Base
attr_accessible :content, :sender_id, :recipient_list
attr_reader :recipient_list #necessary for jquery-token-input
belongs_to :sender, class_name: "User"
validates :content, presence: true
validates :sender_id, presence: true
validates :recipient_list, presence: true
def recipient_list=(recipient) #jquery-token-input
self.recipient_ids = recipients.split(",")
end
end
Controller:
def create
#message = current_user.sent_messages.build(params[:message])
if #message.save
flash[:success] = "Message Sent."
redirect_to '/users/'+current_user.id.to_s+'/messages'
else
redirect_to '/users/'+current_user.id.to_s+'/messages'
end
end
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"WlStV4ogguSX72vrZp10zJbucS5MTL1pT1DLt06qjcw=",
"message"=>{"recipient_list"=>"1,2",
"content"=>"foobar123",
"sender_id"=>"1"},
"commit"=>"Send"}
Result:
#<Message id: 32, content: "foobar123", sender_id: 1, recipient_list: "", created_at: "2012-08-22 19:38:44", updated_at: "2012-08-22 19:38:44">]
What might be the problem that is keeping the recipient_list from being saved in this case?
Edit:
Par Ylan's note I set out to see why it was working despite the difference in variable name.
upon messing with it, I realized that it actually was only working that way if i made recipient -> recipients or the reverse the it would stop working.
Fiddled with it, and based on Nash's suggestion came up with the following:
def recipient_list=(ids)
recipient_list = ids.split(",")
super(recipient_list)
end
#<Message id: 42, content: "foobar123", sender_id: 1, recipient_list: "---\n- '1'\n", created_at: "2012-08-22 21:58:46", updated_at: "2012-08-22 21:58:46">]
So now the recipient_list is being saved, I just have to figure out how to remove all the unecessary garble and get just the '1' lol. Any further suggestions?
Edit #2:
After adding
serialize :recipient_list, Array
#<Message id: 43, content: "foobar123", sender_id: 1, recipient_list: ["1", "2"], created_at: "2012-08-22 22:10:46", updated_at: "2012-08-22 22:10:46">]
is the new out put which is what i was going for. We worked together on this one. Thanks you two.
looks like you should call super method in your overriden writer:
def recipient_list=(recipients) #jquery-token-input
self.recipient_ids = recipients.split(",")
super(recipients)
end
or something similar depends on your code.
I believe you have a typo in your writter method. You are passing an argument named recipient, but call recipients.split(","). Change either one and you should be set.
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'"
How do you persist a derived attribute which depends on the value of id in rails? The snippet below seems to work-- Is there a better rails way?
class Model < ActiveRecord::Base
....
def save
super
#derived_attr column exists in DB
self.derived_attr = compute_attr(self.id)
super
end
end
Callbacks are provided so you should never have to override save. The before_save call in the following code is functionally equivalent to all the code in the question.
I've made set_virtual_attr public so that it can be calculated as needed.
class Model < ActiveRecord::Base
...
# this one line is functionally equivalent to the code in the OP.
before_save :set_virtual_attr
attr_reader :virtual_attr
def set_virtual_attr
self.virtual_attr = compute_attr(self.id)
end
private
def compute_attr
...
end
end
I think the more accepted way to do this would be to provide a custom setter for the virtual attribute and then provide an after_create hook to set the value after the record is created.
The following code should do what you want.
class Virt < ActiveRecord::Base
def after_create()
self.virtual_attr = nil # Set it to anything just to invoke the setter
save # Saving will not invoke this callback again as the record exists
# Do NOT try this in after_save or you will get a Stack Overflow
end
def virtual_attr=(value)
write_attribute(:virtual_attr, "ID: #{self.id} #{value}")
end
end
Running this in the console shows the following
v=Virt.new
=> #<Virt id: nil, virtual_attr: nil, created_at: nil, updated_at: nil>
>> v.save
=> true
>> v
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">
>> Virt.last
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">