Create new records on before_save - ruby-on-rails

While creating new records. I need to create more records for the same model.
Example ::
class XYZ < ActiveRecord
def before_save
# At this point object is already initialized ..
# And it's containing values.
# At this point i want to create 10 more records for the same class.
# something like this
XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
end
end
How may i handle this type of scenario ?
On which call back i have to create more records for the same model ?

First, this sounds like bad engineering, try to rethink your model in a way that makes what you need.
maybe if you need to create 10 models of something, do not use the activerecord hooks, otherwise you might incur in infine loops.
I would recommend
class XYZ < ActiveRecord
def self.create10(original_xyz)
10.times do
clone = original_xyz.clone
clone.save
end
end
end
and where in your controller or wherever you have the need to create 10 more, call:
new_xyz = XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
new_xyz.save
XYZ.create10(new_xyz)
but if you really need to create 10 more on a hook (like before save), do:
class XYZ < ActiveRecord
before_save create10
attr_acessor :cloned
def create10
return if cloned # this will prevent infinit loooooooooooooooop
10.times do
clone = self.clone
clone.cloned = true
clone.save
end
end
end
I did not run this, so, try it first.

class XYZ < ActiveRecord
def after_initialize
# At this point object is already initialized ..
# And it's containing values.
# At this point i want to create 10 moew records for the same class.
# something like this
#XYZ.new(:att1 => value1,:att2 => value2,:att3 => self.att1)
x = 10 #an integer
x.times do |task|
Model.create(:key => :value)
end
end
end

Related

How to call a methods in multiple classes in ruby

class One
class Two
class Three
def name
Faker::Name.name
end
end
def workflow
Three.new
end
end
def event
Two.new
end
def id
Faker::Number.number(4).to_i
end
end
I am new to ruby. Can someone help me, how to call all these methods in ruby?
Is this what you looking for?
one = One.new
two = One::Two.new
three = One::Two::Three.new
three.name
# => "Mr. Dillon Jacobson"
two.workflow
# => #<One::Two::Three:0x000055b2d9d70be0>
one.event
# => #<One::Two:0x000055b2df4160d0>
one.id
# => 6579413068
Pretty simple to do with instance_methods(false) that will give us all the defined instance methods of a class. We can then just push all the nested objs into an array and iterate over them. I'm not too sure on how to get all nested classes. You can however do that with Module.nesting
def call_all_methods(obj)
obj.class.instance_methods(false).each do |m|
obj.public_send(m)
end
end
[
One.new,
One::Two.new,
One::Two::Three.new
].each do |obj|
call_all_methods(obj)
end

Rails populate with default entries after database table was created

I am having a checklist table with following columns:-
id | product_id | content | archived
Once the user signs up product table is created and corresponding checklist table is also created.
I want to add default 5 entries with data for checklist table for each user after he/she signs up. Any help?
I would solve this with a service object.
# app/services/default_checklist_service.rb
class DefaultChecklistService
attr_accessor :user, :defaults
def initialize(user, defaults = nil)
#user = user
#defaults = defaults || self.class.load_defaults
end
def self.call(user, defaults = nil)
new(user, defaults).call
end
def call
self.defaults.map do |attributes|
user.checklists.create(attributes)
end
end
private
def self.load_defaults
YAML.load_file(Rails.root.join('config', 'default_checklist.yml'))
.try(:[], 'checklists')
end
end
This creates a single purpose object which is easily tested. VS model callbacks which add more responsibilities to an already god like model and which are tricky to test and control when and where they fire.
# config/default_checklists.yml
checklists:
-
foo: 1
bar: 2
-
foo: 2
bar: 3
Note that this will create n (where n is the number of default items) separate insert queries which is not very fast. If the performance becomes an issue than you can use a mass insert instead:
# app/services/default_checklist_service.rb
class DefaultChecklistService
# ...
def call
sql = "INSERT INTO users(user_id, foo, bar) VALUES "
values = defaults.map do |a|
"(#{#user.id}, #{a["foo"]}, #{a["bar"]})"
end
ActiveRecord::Base.connection.execute( sql + values.join(', ') )
end
end
Since creating the defaults is expensive and will slow your tests down I would not use a model callback. Instead call the service from the controller where you actually want the seeding to happen:
class UsersController
def create
#user = User.create(user_params)
if #user.save
DefaultChecklistService.call(#user)
# ...
else
# ...
end
end
end
Use after create callback:
User < ActiveRecord::Base
has_many :check_lists
after_create :add_default_checklist
def add_default_checklist
default_check_lists.each do |check_list_data| # Define way to get default values
check_lists.create(check_list_data)
end
end
end
You'll need to add user_id column to checklist

How to pass dynamic params in Rails?

I want some of my model attributes to predefined dynamically. I have various models.And now I want My Bill model to create objects using other model instances.
Models :
leave.rb # belongs_to :residents
resident.rb # has_many:leaves,has_many:bills,has_one:account
bill.rb # belongs_to:residents
rate_card.rb # belongs_to:hostel
account.rb # belongs_to:resident
hostel.rb
now here is my bills controller create method :
def create
#bill = Resident.all.each { |resident| resident.bills.create(?) }
if #bill.save
flash[:success]="Bills successfully generated"
else
flash[:danger]="Something went wrong please try again !"
end
end
I want to build bill using all of the models eg:
resident.bills.create(is_date:using form,to_date:using form,expiry_date:using form,amount:30*(resident.rate_card.diet)+resident.rate_card.charge1+resident.rate_card.charge2)+(resident.account.leaves)*10+resident.account.fine)
///////Is this possible ?
And how to use strong params here ?
Pls help me out thxx..
I think the Rails way for this logic you want is with callbacks if you want calculated attributes either on create, update or delete, meaning attributes that depend on other models. For instance:
class Bill < ActiveRecord::Base
...
before_create :set_amount
...
protected
def set_amount
self.amount = 30 * self.resident.rate_card.diet + self.resident.rate_card.charge1 + self.resident.rate_card.charge2 + (self.resident.account.leaves) * 10 + self.resident.account.fine
end
end
If you want this logic to be used when updating the record also, then you should use before_save instead of before_create.
After you do this, you should accept the usual params (strong) of Bill model, as in:
def bill_params
params.require(:bill).permit(:is_date, :to_date, :expiry_date)
end
So your create call would be like:
resident.bills.create(bill_params)
Also, be wary of your create action, you should probably create a method either on your Bill or your Resident model that uses transactions to create all bills at the same time because you probably want either every bill created or none. This way you won't have the Resident.all.each logic in your BillsController.
create takes a hash, you can:
create_params = { amount: 30*(resident.rate_card.diet) }
create_params[:some_field] = params[:some_field]
# and so on
resident.bills.create(create_params)
or:
obj = resident.bills.build(your_strong_parameters_as_usual)
obj.amount = # that calculation
obj.save!
I'm confused at your syntax of your controller. #bill is being set to the value of a loop, which feels off. Each loops return the enumerable you cycle through, so you'll end up with #bill = Resident.all with some bills being created on the side.
What your controller really wants to know is, did my many new bills save correctly?
This seems like a perfect place to use a ruby object (or, colloquially, a Plain Old Ruby Object, as opposed to an ActiveRecord object) to encapsulate the specifics of this bill-generator.
If I'm reading this right, it appears that you are generating many bills at once, based on form-inputted data like:
is_date
to_date
expiry_date
...as well as some data about each individual resident.
Here's the model I'd create:
app/models/bill_generator.rb
class BillGenerator
include ActiveModel::Model
# This lets you do validations
attr_accessor :is_date, :to_date, :expiry_date
# This lets your form builder see these attributes when you go form.input
attr_accessor :bills
# ...for the bills we'll be generating in a sec
validates_presence_of :is_date, :to_date, :expiry_date
# You can do other validations here. Just an example.
validate :bills_are_valid?
def initialize(attributes = {})
super # This calls the Active Model initializer
build_new_bills # Called as soon as you do BillGenerator.new
end
def build_new_bills
#bills = []
Resident.all.each do |r|
#bills << r.bills.build(
# Your logic goes here. Not sure what goes into a bill-building...
# Note that I'm building (which means not-yet-saved), not creating
)
end
def save
if valid?
#bills.each { |b| b.save }
true
else
false
end
end
private
def bills_are_valid?
bill_validity = true
#bills.each do |b|
bill_validity = false unless b.valid?
end
bill_validity
end
end
Why all this mess? Because in your controller you can do...
app/controllers/bill_controller.rb
def create
#bill_generator = BillGenerator.new(bill_generator_params)
if #bill_generator.save?
# Redirect to somewhere with a flash?
else
# Re-render the form with a flash?
end
end
def bill_generator_params
params.require(:bill_generator).permit(:is_date, :to_date, :expiry_date)
# No extra garbage. No insecurity by letting all kinds of crud through!
end
...like a BillGenerator is any old object. Did it save? Great. It didn't, show the form again.
Now, my BillGenerator won't just be copy-and-paste. Your 'build_new_bills' probably will have some of that math you alluded to, which I'll leave to you.
Let me know what you think!
you can do it by using params.permit! as this allows any parameters to be passed. here's an example:
def create
...
#bill = Resident.all.each { |resident| resident.bills.create(any_params) }
end
private
def any_params
params.permit!
end
be careful with this of course, as you are opening this up to potential exploits.

Recommended practice for passing current user to model

Given a model Orderstatus with attributes private_status:string, and private_status_history:json(I'm using Postgresql's json). I would like to record each status transition, together with the user who made the change.
Ideally it would be something like:
class Orderstatus < ActiveRecord::Base
after_save :track_changes
def track_changes
changes = self.changes
if self.private_status_changed?
self.private_status_history_will_change!
self.private_status_history.append({
type: changes[:private_status],
user: current_user.id
})
end
end
end
class OrderstatusController <ApplicationController
def update
if #status.update_attributes(white_params)
# Good response
else
# Bad response
end
end
end
#Desired behaviour (process not run with console)
status = Orderstatus.new(private_status:'one')
status.private_status #=> 'one'
status.private_status_history #=> []
status.update_attributes({:private_status=>'two'}) #=>true
status.private_status #=> 'two'
status.private_status_history #=> [{type:['one','two'],user:32]
What would be the recommended practice to achieve this? Apart from the usual one using Thread. Or maybe, any suggestion to refactor the structure of the app?
So, I finally settled for this option ( I hope it's not alarming to anyone :S)
class Orderstatus < ActiveRecord::Base
after_save :track_changes
attr_accessor :modifying_user
def track_changes
changes = self.changes
if self.private_status_changed?
newchange = {type:changes[:private_status],user: modifying_user.id}
self.update_column(:private_status_history,
self.private_status_history.append(newchange))
end
end
end
class OrderstatusController <ApplicationController
def update
#status.modifying_user = current_user # <---- HERE!
if #status.update_attributes(white_params)
# Good response
else
# Bad response
end
end
end
Notes:
- I pass the from the Controller to the Model through an instance attribute modifying_user of the class Orderstatus. That attribute is ofc not saved to the db.
- Change of method to append new changes to the history field. I.e. attr_will_change! + save to update_column + append

How to write a method trailing with a keyword

I have a situation where i need to call something like this :
class Office
attr_accessor :workers, :id
def initialize
#workers = []
end
def workers<<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#id}/workers.json", :worker => {:type => type})
end
end
this is where i need to call
office = Office.new()
new_worker = Worker.new()
office.workers << new_worker
how should i modify the above workers method in order to implement above code.
New answer for this (based on updated question):
class WorkersClient
attr_accessor :office_id
def <<(worker)
type = worker.type
resp = Organiation::Worker.post("/office/#{#office_id}/workers.json", :worker => {:type => type})
end
end
class Office
attr_accessor :workers, :id
def initialize
#workers = WorkersClient.new
#workers.office_id = #id
end
end
I'm assuming that the Worker class is defined somewhere, something like:
def Worker
attr_accessor :type
...
end
The WorkersClient class is just a proxy to handle the collection (like ActiveRecord 3 does with associations). You can develop it further to store a local cache of workers, and so on.
I would recommend looking at how Rails' ActiveResource is implemented, as it does something VERY similar.
try this office.build_worker
If those objects are actually ActiveRecord objects (which it sort of sounds like), you're probably looking at
office.workers << new_worker
Note the plural form.
If those objects are your own creations, you probably want Office#workers to return an Array'ish object, so something like
class Office
def workers
#workers ||= []
end
end
Add sanity checks and whatnot as you see fit.
There's not much to add to what's already been said, but one thing to think about is hiding the implementation of workers. Sure, it starts out with an array, but that may change. By implementing your own << method you can hide implementation details from the user.
class Office
attr_accessor :workers
def initialize
#workers = []
end
def <<(other)
self.workers << other
end
end
I tend to use getter/setters inside my classes as that's something I learned from Smalltalk books, but of course you could just do #workers << other.

Resources