Set default random column value in ActiveRecord model - ruby-on-rails

got 2 models:
class User < ActiveRecord::Base
has_many :posts
end
and
class Post < ActiveRecord::Base
belongs_to :user
end
the Posts table has a column: u_hash. This is supposed to be a randomly generated identifying hash (for public viewing). What is the best way to generate this hash and how can I add it to the table? The idea is that all this will happen in the background and not be visible to the user (no hidden field in the form). The database used is MySQL if that could help me out somehow.
Thanks in advance!
J

You most likely need before_validation_on_create callback for your Post model. This callback is internally called by ActiveRecord functionality when you save a new Post record into database.
A good callback reference and a hint of the order callbacks are called in you can find here.
Here's a code, that explains why it is before_validation_on_create that you need to use:
class Post < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :u_hash
before_validation_on_create :generate_u_hash
def generate_u_hash
begin
new_u_hash = "random hash here"
end while Post.find_by_u_hash(new_u_hash)
self.u_hash = new_u_hash
end
end

This sounds like a job for ActiveRecord callbacks.
If your posts tables has a before_create callback, you can create and set a value automatically every time a new post instance is created.
e.g.:
class Post < ActiveRecord::Base
belongs_to :user
before_create :set_uhash_column
private
def set_uhash_column
#your code here - something like self.uhash = ...
end
end

Related

How to set attributes piecemeal in Rails

I have a model, say Question, with fields time_asked and time_answered. I want to be able to set time_asked when the question is served, and time_answered when a response is posted. Is this possible and how might I do it?
You can use active record callbacks to do so.
Suppose you have a separate class Response and it belongs to Question.
So your code for time_asked will look like:
class Question < ActiveRecord::Base
has_many :responses
before_create :fill_time_asked
private
def fill_time_asked
time_asked = Time.now
end
end
response.rb:
class Response < ActiveRecord::Base
belongs_to :question
before_create :fill_time_answered
private
def fill_time_answered
question.time_aswered = Time.now
end
end
Another way to do it is to manually call proper methods, e.g. question.asked! or question.answered! from your controllers. However, the rails way is to keep business logic in your models and have thin controllers.

How can I check whether an associated array of records was changed in after_save callback for ActiveRecord?

I have code like the following:
class MyModel < ActiveRecord::Base
has_many :associated_records
accepts_nested_attributes_for :associated_records
after_save :send_notification, if: :relevant_data_changed?
def relevant_data_changed?
return self.some_column_changed? || self.associated_records.changed?
end
def send_notification
# Do stuff
end
end
I know I can check whether a column that is directly on the model changed (as I did in the example), and I think you can similarly even check whether a single nested object changed if there is a has_one relationship with that object (via self.nested_model.changed? I believe), but I can't figure out how to check whether an array of objects has changed, such as associated_records in my example.
EDIT: For the record, I did try the suggested solution from here: Rails: if has_many relationship changed. But it does not work in the case where objects were only added or removed rather than actually changed.
Does anyone know of a way I can do this? Thanks.
Something like this should work:
class MyModel < ActiveRecord::Base
has_many :associated_records, after_save :force_save_associated_records
def force_save_associated_records
associated_records.map{|x| x.save! if x.changed?}
end
end
So after a bit more digging, it turns out that the proper way to do what I'm trying to do is with the after_add and after_remove callbacks.
How can I validate that has_many relations are not changed
In my example, it would be something like:
has_many :associated_records, after_add: :send_notification
# This gets called for every new record added, even if multiple are
# added at once
def send_notification(new_record)
# Do stuff with the new_record
end

Complex After save association in ruby on rails

Theory :- after create of a record in customer bill, i am sending two sets of data two different models. one set of data is sent to ledger and one set of data is sent to ledger_line_item. the complexity is that after sending of data i want the ledger_id to be stored in ledger_line_item. the code is as follows
code :-
class CustomerBill < ActiveRecord::Base
after_create :creating_ledger_line_items, :creating_ledger_items
def creating_ledger_items
CustomerLedger.create(:customer_id =>self.customer_id,/*rest of attributes*/)
end
def creating_ledger_line_items
CustomerLedgerLineItem.create(:customer_id =>self.customer_id,/*rest of attributes*/)
end
end
in ledger i have written
class CustomerLedger < ActiveRecord::Base
after_save :update_record_line_items
def update_record_line_items
a = CustomerLedgerLineItem.find_by_customer_id(self.customer_id)
a.update_attributes(:customer_ledger_id => self.id)
end
end
the above code works fine without error but the ledger_id is not been posted in ledger_line_items. i am not able to determine why this error is happening? is there any other way i can achieve my goal of posting ledger_id in ledger_line_items after a bill is created?
Guidance Required. Thanking you in advance.
You can change your models something as follows.:
I am assuming you have Customer Model.
class Customer < ActiveRecord::Base
has_one :customer_ledger
has_many :customer_ledger_line_items, :through => :customer_ledger
accepts_nested_attributes_for :customer_ledger
end
class CustomerLedger < ActiveRecord::Base
has_many :customer_ledger_line_items
accepts_nested_attributes_for :customer_ledger_line_items
end
class CustomerBill < ActiveRecord::Base
belongs_to :customer
after_create :creating_ledger_items, :creating_ledger_line_items
def creating_ledger_line_items
cl = self.customer.customer_ledger.build(your_attributes)
cl.save!
end
def creating_ledger_items
cli = self.customer.customer_ledger.customer_ledger_items.build(your_attributes)
cli.save!
end
end
In case you want to create the models on an *after_create* hook, I'll explain what's the problem.
When you create a model in rails, and you have hooks like *after_create*, *before_update*, etc. all the updates happens in a Transaction, so if any of them throws an exception, nothing is updated.
In this case, within a Transaction, you are trying to get the ID of a CustomerLedger that doesn't exists yet, because since everything is within a Transaction, the record is not saved to the database until the transaction is executed, and thats the reason that on CustomerLedger#update_record_line_items, self.id is always nil.
Using the nested attributes proposed by codeit is probably the best solution to your problem, but if you feel that nested attributes its an advance topic, you can do something like:
class CustomerBill < ActiveRecord::Base
after_create :created_leder_data
def create_ledger_data
customer_ledger = CustomerLedger.build(customer_id: self.customer_id, # Rest of attributes)
customer_ledger.customer_ledger_line_items.build(customer_id: self.customer_id, # Rest of attributes)
customer_ledger.save!
end
end

rails passing parameters to callback

I have two models
class Department < ActiveRecord::Base
has_many :checklists
attr_accessible :deadline
after_update :update_checklist
class Checklist < ActiveRecord::Base
belongs_to :department
attr_accessible :content, :category
Basically, the 'department' model has a virtual attribute called 'deadline', and it is in type of date. The actual value of 'deadline' is stored in another model 'checklist', in format of string.
Every time when 'deadline' is updated, I would like to check if there is an entry in 'checklist', and create (if not yet) or update (if already has an entry).
I was thinking this way
def deadline=(deadline)
#cl = Checklist.find_or_create_by_department_id_and_category(self.id, 'deadline')
#cl.update_attributes(:content => deadline.to_s)
#cl.save
end
def deadline
#deadline = self.checklists.find_by_category('deadline')
Date.parse(#deadline.to_s)
end
But the above virtual attribute is not working.
When searching for the answer, I found on rails cast that callback will be a better solution for this kind of situation. So I am trying to something like:
class Department < ActiveRecord::Base
after_update :update_checklist
def update_checklist
#cl = Checklist.find_or_create_by_department_id_and_category(self.id, 'deadline')
#cl.update_attributes(:content => ???)
end
I am not sure how to pass the value to the callback.
Please help me with this design. what is the standard way to handle this? Thank you in advance!
update_checklist is a method of Department. So within update_checklist, you can access any Department attributes, just like self.id, self.deadline is what you want.

Reindex just one particular record with sunspot

I have two models
class User < ActiveRecord::Base
has_many :posts
searchable do
text :post_titles
end
def post_titles
posts.map &:title
end
end
class Post < ActiveRecord::Base
belongs_to :user
end
the problem is that when I update title of the Post sunspot doesn't update index for related user and it is not searchable by new data. If I do User.index it solves problem but takes too much time. Are there any better solutions to update parent record index on child record change(like reindex just parent record and not all users)?
Sunspot provides an instance index() method, for indexing one record.
What I did was
class Post
belongs_to :user
after_save :update_user_index
private
def update_user_index
user.index
end
end
If you are running this in console, and want to see results immediately, call Sunspot.commit

Resources