How to initialize an ActiveRecord with values in Rails? - ruby-on-rails

In plain java I'd use:
public User(String name, String email) {
this.name = name;
this.email = f(email);
this.admin = false;
}
However, I couldn't find a simple standard way to do in rails (3.2.3), with ActiveRecords.
1. override initialize
def initialize(attributes = {}, options = {})
#name = attributes[:name]
#email = f(attributes[:email])
#admin = false
end
but it might be missed when creating a record from the DB
2. using the after_initialize callback
by overriding it:
def after_initialize(attributes = {}, options = {})
...
end
or with the macro:
after_initialize : my_own_little_init
def my_own_little_init(attributes = {}, options = {})
...
end
but there may be some deprecation issues.
There are some other links in SO, but they may be out-of-date.
So, what's the correct/standard method to use?

Your default values should be defined in your Schema when they will apply to ALL records. So
def change
creates_table :posts do |t|
t.boolean :published, default: false
t.string :title
t.text :content
t.references :author
t.timestamps
end
end
Here, every new Post will have false for published. If you want default values at the object level, it's best to use Factory style implementations:
User.build_admin(params)
def self.build_admin(params)
user = User.new(params)
user.admin = true
user
end

According to Rails Guides the best way to do this is with the after_initialize. Because with the initialize we have to declare the super, so it is best to use the callback.

One solution that I like is via scopes:
class User ...
scope :admins, where(admin: true)
Then you can do both: create new User in the admin status(i.e. with admin==true) via User.admins.new(...) and also fetch all your admins in the same way User.admins.
You can make few scopes and use few of them as templates for creating/searching. Also you can use default_scope with the same meaning, but without a name as it is applied by default.

I was searching for something similar this morning. While setting a default value in the database will obviously work, it seems to break with Rails' convention of having data integrity (and therefore default values?) handled by the application.
I stumbled across this post. As you might not want to save the record to the database immediately, I think the best way is to overwrite the initialize method with a call to write_attribute().
def initialize
super
write_attribute(name, "John Doe")
write_attribute(email, f(email))
write_attribute(admin, false)
end

This will work in rails 4.
def initialize(params)
super
params[:name] = params[:name] + "xyz"
write_attribute(:name, params[:name])
write_attribute(:some_other_field, "stuff")
write_attribute(:email, params[:email])
write_attribute(:admin, false)
end

Related

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.

Defaulting area_code to true for number_to_phone in rails

I'm using NumberHelper's number_to_phone method many times in my app. It looks like this...
number_to_phone(phone_number, area_code: true)
But there's never a place where I want the area_code to be false. How should I have it default to true?
One way to do this would be to write your own method that only takes a phone number argument and some options, merges the options with a default value for :area_code, and calls #number_to_phone. You could do this in ApplicationHelper like so:
# application_helper.rb
def num_to_phone(phone_number, opts={})
opts = {area_code: true}.merge(opts)
number_to_phone(phone_number, opts)
end
This way, you can just use your wrapper method without having to worry about trying to monkey patch the original one.
I guess this is what I was looking for.
# application_helper.rb
def formatted_phone(number, options={area_code: true})
number_to_phone(number, options)
end
1) You can set area_code: true through callback method like:-
Class Model
before_create :set_area_code_to_true
private
def set_area_code_to_true
self.area_code = true
end
end
2) You can set default value of area code, when added a new attribute in table through migration like:-
rails g migration add_default_value_to_table
def change
change_column :table_name, :area_code, :boolean, default: true
end

How can I only allow updating of nil attributes for a Rails ActiveRecord object?

I have an ActiveRecord object with multiple attributes that are allowed to be nil on creation and can later be updated by the user through a form. However, once an attribute is changed from nil to non-nil, that attribute may not be updated again. How should I go about setting up this behavior?
create_table :funky do |t|
t.integer :fireflies
end
class Funky < ActiveRecord::Base
def fireflies=(ff)
raise "Uh uh.. it was already set" unless self.fireflies.blank?
write_attribute(:fireflies, ff)
end
end
Editing post as user requested that many fields be edited
[:one, :two, :three].each do |s|
define_method "#{s}=" do |v|
raise "Uh uh.. it was already set" unless self.send(s).blank?
write_attribute(s, v)
end
end
Well, I think you should definitely allow users to change their information at any time, but anyway, if you want to add the restriction to the controller instead of the model you could do this:
def update
your_model = YourModel.find(params[:id])
# find the attributes with nil values:
nil_attributes = your_model.attributes.select {|k,v| v.nil?}.keys.map(&:to_sym)
# attributes that you allow to edit:
allowed_attributes = [:title, :description, :size]
# tell rails which are the allowed modifications:
allowed_params = params.require(:your_model).permit(*(allowed_attributes - nil_attributes))
# save the changes:
your_model.update_attributes(allowed_params)
# ...
end

Is it possible to omit "eval" in favor of "call" or "send" somehow in my case (Rails app)?

I'm newbie in Ruby, so need help, because can not find answer :(
I have Rails application, which has model Event like this:
class Event < ActiveRecord::Base
before_validation :clean_input
....
protected
def clean_input
fields = %w[title preview content]
fields.each do |field|
eval "self.#{field} = ActionController::Base.helpers.sanitize(self.#{field})"
end
end
end
The method purpose is cleaning input from dangerous data before validation and before storing it in DB.
Before I wrote this method it looked like the one below (with lot of duplication, that is not DRY at all). This code is very clear, but when adding new field I'll have to add new line instead of adding new element to an array:
def clean_input
self.title = ActionController::Base.helpers.sanitize(self.title)
self.preview = ActionController::Base.helpers.sanitize(self.preview)
self.content = ActionController::Base.helpers.sanitize(self.content)
end
So my questions are:
1) is it possible to omit eval ... in favor of call or send somehow (all my attemps were useless)?
2) is it possible to declare before_validation :clean_input like this before_validation clean_input: fields: { :title, :preview, :content}?
1) Sure:
def clean_input
%w(title preview content).each do |field|
self.send("#{field}=", ActionController::Base.helpers.sanitize(self.send(field)))
end
end
2) No, and your current implementation is ok
Since you are updating an active record model, there are a few other ways of updating attributes, for example:
def clean_input
%i(title preview content).each do |field|
self[field] = ActionController::Base.helpers.sanitize(self[field])
end
end

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

Resources