Rails 4 - Prepopulating a new record from another Model - ruby-on-rails

I have two models: Bid and Template. Each new bid that is created, needs to prepopulate some attributes with values from attributes of an existing Template instance. First, would I do this in the controller? That is what I am currently trying to do. My new action looks like this:
(note:I'm using the wicked gem to build the object in steps)
def new
#bid = Bid.new
prepopulate(#bid.id)
redirect_to wizard_path(steps.first, :bid_id => #bid.id)
end
My prepopulate method:
def prepopulate(bid_id)
#bid = Bid.find(bid_id)
#template = Template.find(:first)
#bid.update_attribute(attribute_1: #template.attribute_1)
end
It doesn't update the attributes in the Bid. It doesn't raise any errors either. Is there a better way to accomplish what I'm looking to do? If not, how can I update the attributes from my Template instance?

This is definitively model concern, so it should be moved into your model:
Slightly better way:
Model:
def prepopulate
template = Template.find(:first)
attribute_1 = template.attribute_1
end
Controller:
#bid = Bid.new # create?
#bid.prepopulate
The correct way:
However since you need to do it for every single Bid, just use after_initialize hook method for that:
class Bid < AR::Base
after_initialize :prepopulate
private
def prepopulate
return unless new_record?
template = Template.first
self.attribute_1 = template.attribute_1
end
end
end
This approach doesn't require you to do anything in your controller, as simply calling new do all you need.

Related

How to set different values for setters per active record object in ruby rails?

I am trying to assign session values to model object as below.
# models/product.rb
attr_accessor :selected_currency_id, :selected_currency_rate, :selected_currency_icon
def initialize(obj = {})
selected_currency_id = obj[:currency_id]
selected_currency_rate = obj[:currency_rate]
selected_currency_icon = obj[:currency]
end
but this works only when I initialize new Product object
selected_currency = (session[:currency].present? ? session : Currency.first.attributes)
Product.new(selected_currency)
While, i need to set these setter methods on each product object automatically even if was fetched from Database.(active record object) ie. Product.all or Product.first
Earlier i was manually assigning values to each product object after retrieving it from db on controller side.
#products.each do |product|
product.selected_currency_id = session[:currency_id]
product.selected_currency_rate = session[:currency_rate]
product.selected_currency_icon = session[:currency]
end
But then i need to do it on every method where product details need to be displayed. Please suggest a better alternative to set these setter methods automatically on activerecord objects.
I don't think you really want to do this on the model layer at all. One thing you definitely don't want to do is override the initializer on your model and change its signature and not call super.
Your model should only really know about its own currency. Displaying the price in another currency should be the concern of another object such as a decorator or a helper method.
For example a really naive implementation would be:
class ProductDecorator < SimpleDelegator
attr_accessor :selected_currency
def initialize(product, **options)
# Dynamically sets the ivars if a setter exists
options.each do |k,v|
self.send "#{k}=", v if self.respond_to? "#{k}="
end
super(product) # sets up delegation
end
def price_in_selected_currency
"#{ price * selected_currency.rate } #{selected_currency.icon}"
end
end
class Product
def self.decorate(**options)
self.map { |product| product.decorate(options) }
end
def decorate(**options)
ProductDecorator.new(self, options)
end
end
You would then decorate the model instances in your controller:
class ProductsController
before_action :set_selected_currency
def index
#products = Product.all
.decorate(selected_currency: #selected_currency)
end
def show
#product = Product.find(params[:id])
.decorate(selected_currency: #selected_currency)
end
private
def set_selected_currency
#selected_currency = Currency.find(params[:selected_currency_id])
end
end
But you don't need to reinvent the wheel, there are numerous implementations of the decorator pattern like Draper and dealing with currency localization is complex and you really want to look at using a library like the money gem to handle the complexity.

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.

Ruby on Rails - Creating and Using a Custom Method

I am rather new to Rails, and would greatly appreciate any bit of help. I have created the following method:
def name_fix
name = self.split
mod_name = []
name.each do |n|
n.split("")
if n[0]
n.upcase
else
n.downcase
end
mod_name.push(n)
end
mod_name.join
end
I would like to use this method in my Controller as such:
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name].name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
How can I go about accomplishing this? Will this method reside within my Model or Controller? Previously, I've run into an undefined method error.
Note: I'm sure that there is a way to better write my code. I am grateful for help with that as well.
#app/models/patient.rb
class Patient < ActiveRecord::Base
protected
def name=(value)
mod_name = []
value.split.each do |n|
n.split("")
type = n[0] ? "up" : "down"
n.send("#{type}case")
mod_name.push(n)
end
#name = mod_name.join
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient = Patient.new patient_params
#patient.save ? redirect_to(patients_path) : render(:new)
end
private
def patient_params
params.require(:patient).permit(:name)
end
end
What you're doing is trying to override the setter method, which can be done using the above code. Much more efficient and out of the way.
I have created the following method
Since you're new, let me explain something else.
It is important to note where you're using this method.
You've currently put it in the model, which means you'll have to call it to manipulate some attribute / functionality of any object created with said model.
--
Models - in Rails - build the objects which populate your app. Ruby is an object orientated language, which means that every element of your program should revolve around data objects in some degree.
As you can see above, the method of building objects in your system is really about invoking classes. These classes contain methods which can be called, either at class level (IE invoking the class through the method), or at instance level (IE calling a method on an already invoked object).
This is where you get "class" methods (Model.method) and "instance" methods (#model.method) from:
#app/models/patient.rb
class Patient < ActiveRecord::Base
def explode
#this is an instance method
puts "Instance Explode"
end
def self.explode
#this is a class method
puts "Exploded"
end
end
Thus you can call the following:
#patient = Patient.find params[:id]
#patient.explode #-> "Instance explode"
Patient.explode #-> "Exploded"
--
This is important because it gives you a strict framework of where you should, and shouldn't use methods in your models.
It explains why you have controllers & helpers, and allows you to formulate the best way to structure your application as to get the most out of the least code.
For example...
Your use of #patient.name = params[:params][:name].name_fix is incorrect.
It's wrong because you're calling the instance method .name_fix on a piece of data totally unrelated to your model. If you wanted to use .name_fix in a general sense like this, you'd probably use a helper:
#app/helpers/patients_helper.rb
class PatientsHelper
def name_fix value
# stuff here
end
end
#app/controllers/patients_controller.rb
class PatientsController < ApplicationController
def create
#patient.name = name_fix params[:patient][:name]
end
end
Since you're using the method to populate the .name attribute of your model, it makes sense to override the name= setter. This will not only provide added functionality, but is much smoother and efficient than any other way.
Methods that are called directly are best put in the Controller (or in ApplicationController if you think more than one controller might want to use it).
These are methods like
# app/controllers/my_controller.rb
def foo(bar)
# do something here
end
def create
id = params[:id]
value = foo(id)
end
If you want a chained method that acts as a property method of whatever you're calling it on. Those are characteristic of how Models work - you have your main model and you call attributes or methods on the instance of that model.
# app/models/my_model.rb
def full_name
first_name + " " + last_name
end
# app/controller/my_controller.rb
def create
id = params[:id]
model = MyModel.find(id)
full_name = model.full_name
end
In your case, you want to call name_fix ON whatever is returned by params[:params][:name], which is (I'm guessing) a String.
You have two options
Modify the String class to define a method named name_fix. I highly recommend against this. It's call "monkeypatching" and shouldn't be done without good reason. Just letting you know you can do it in some cases.
Use a direct method in your controller or ApplicationController like the first example above.
#patient.name = name_fix(params[:params][:name])
Edit: As for your request about a better way to write your code... that's difficult to teach or convey in one answer. I'd say read some open source projects out there to see how people write Ruby and some common idioms used to clean up the code. To get you started, here's how I'd re-write your code
def create
#patient = Patient.new(params[:patient])
# 1. Be descriptive with your method names. `name_fix` is vague
# 2. Why is `:name` nested under another `[:params]` hash?
#patient.name = capitalize_name(params[:name])
if #patient.save
# 1. I think `patient_path` has to be singular
# 2. It needs a `Patient` object to know how to construct the URL
# e.g. `/patients/:id`
redirect_to patient_path(#patient)
else
render :new
end
end
def capitalize_name(full_name)
# Example: julio jones
#
# 1. `split` produces an array => ["julio", "jones"]
# 2. `map` applies a function (`capitalize`) to each element
# => ["Julio", "Jones"]
# 3. `join(" ")` rejoins it => "Julio Jones"
full_name.split.map(&:capitalize).join(" ")
end
Assuming your goal with the name_fix method is just to capitalize the first letter of each name, you could just pass name as an argument and store it as a private method on the Controller:
# app/controllers/patient_controller.rb
private
def name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do
#patient.name = name_fix(params[:params][:name])
in the create method.
OR, you could store this method in the model:
# app/models/patient.rb
def self.name_fix(name)
name.split.map(&:capitalize).join(" ")
end
Then you could do this instead, in the controller:
#patient.name = Patient.name_fix(params[:params][:name])
I would also suggest renaming your name_fix method to something like capitalize_name.
update your create method as below
def create
#patient = Patient.new(params[:patient])
#patient.name = params[:params][:name]
#patient = #patient.name_fix
if #patient.save
redirect_to patients_path
else
render :new
end
end
It should work.

Ruby on Rails: how do I create an initializer method in the model or should I set all the default params in a controller?

I have a field in my model for date created, this is not passed from the form and is currently set in the create method of my controller.
Should this be in my model instead in some sort of initializer method? If so what would that method look like?
I have other fields I want to set as a default each a record is created so I'm trying to find out where is the accepted standard place to put these. I'm starting to think it should be the model as if the model was ever called outside the controller it wouldn't have all this logic.
I generally create builders and never use directly the standard Rails method create.
The point is to gather all the logic in one place with particular cases etc...
Basically in controllers I end up calling the builders this way:
#my_model_instance = MyModelBuilder.new(current_user, params[:my_model]).build
#my_model_instance = MyModelBuilder.new(current_user, params[:my_model]).create
All my builders live in /app/builders
Here is a very basic example:
class MyModelBuilder
attr_accessor :params, :user, :my_model
# consider using a Struct if you keep a very basic initializer
def initialize(user, params)
self.user = user
self.params = params
end
def build
my_model
end
def create
my_model.tap{|m| m.save }
end
def my_model
#my_model ||= MyModel.new(default_values.merge(params))
end
def default_values
{
foo: 'bar'
}
end
end
Rails already manages the date of creation and update of your records.
If your model has a created_at field or an updated_at field they will be filled with the time of creation and update of your model.
You can generate those fields easily in a migration, for instance :
create_table :hello do
t.timestamps
end
Now, for default values, you can fill them in the initialize method of the model :
def initialize(*args)
self.field = default_value
super(*args)
end

How to calculate difference of two fields on form submit Ruby on Rails

I am new to Ruby on Rails and have been using Scaffolding. On one of the forms I want to be able to have two fields and then have the difference between the two submitted to the database.
Instead of :pickupamount, I want (Ending Amount - Starting Amount) to be calculated and entered into the pickupamount column of my database.
Thanks in advance!
You could do this in either your model or your controller. Going along with Skinny Controller Fat Model, it might be better to put the functionality in your model. Check out ActiveRecord callbacks.
class MyModel < ActiveRecord::Base
attr_accessor :start_amount, :end_amount
before_create :calculate_pickup_amount
private
def calculate_pickup_amount
self.pickupamount = end_amount.to_i - start_amount.to_i
end
end
Then, in your controller:
def create
# Assuming params[:my_model] has all the data for initializing a MyModel,
# including start_amount and end_amount but not pickupamount:
my_model = MyModel.new(params[:my_model])
if my_model.save
# Yay, do something
else
# Fail, do something else
end
end
It might be useful to include the following extension method to Ruby's String class (thanks to sikelianos), perhaps in a file in your Rails app's lib directory:
class String
def numeric?
Float self rescue false
end
end
Then you could perform a check before setting pickupamount:
def calculate_pickup_amount
if end_amount.numeric? && start_amount.numeric?
self.pickupamount = end_amount.to_i - start_amount.to_i
else
# Throw exception, set some default value, etc.
end
end
In your view:
form_tag '/some_action' do
text_field_tag 'start_amount'
text_field_tag 'end_amount'
end
In your controller:
def create
model = Model.new
model.pickupamount = params[:end_amount].to_i - params[:start_amount].to_i
model.save!
end

Resources