Set a default value conditionally - ruby-on-rails

I'm trying to figure out how to set a default value of a field based on some conditions.
Right now, a field room_type is defaulted to double, regardless of the number of people in a room.
Basically I'm aiming for something along the lines of:
def set_room_defaults
if people_in_room.size == 1
set room_type = "Single"
elsif people_in_room.size == 2
set room_type = "Double"
elsif people_in_room.size == 3
set room_type = "Triple"
end
end
I've been looking into the default_value_for gem, hoping I could do something like:
default_value_for :room_type, (call set_room_default)
I've read a bit that after_initialize might be close to what I'm looking for?
Is something like this possible? Or am I going about this all wrong?
Thanks

From the API docs
after_find and after_initialize callback is triggered for each object that is found and instantiated by a finder, with after_initialize being triggered after new objects are instantiated as well.
You could do something along the lines of:
class Room < ApplicationRecord
after_initialize :set_room_defaults
def set_room_defaults
if people_in_room.size == 1
set room_type = "Single"
elsif people_in_room.size == 2
set room_type = "Double"
elsif people_in_room.size == 3
set room_type = "Triple"
end
end
end
Or you could store the mappings in an Array:
class Room < ApplicationRecord
ROOM_TYPES = %w(Single Double Triple).freeze
after_initialize :set_room_defaults
def set_room_defaults
set room_type = ROOM_TYPES[people_in_room.size - 1]
end
end

Related

How to convert string to existing attribute in model when creation

I got a array of strings, I want to retrieve for each the attribute during the creation of the post.
My array = ["_646_maturity", "_660_maturity", "_651_maturity", "_652_maturity", "_641_maturity"]
class Audit < ApplicationRecord
belongs_to :user
before_save :calculate_scoring
def calculate_scoring
scoring = []
models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize.constantize rescue nil}
columns = models.collect{|m| m.column_names rescue nil}
columns[2].each do |c|
if c.include? "maturity"
Rails.logger.debug 'COLUMN : '+c.inspect
scoring.push(c)
end
end
getMaturity = ""
scoring.each do |e|
getMaturity = e.to_sym.inspect
Rails.logger.debug 'MATURITY : '+getMaturity
end
end
end
The log print > 'MATURITY : :_651_maturity'
I'm looking to the value of :_651_maturity who is a attribute of my post.
I tried .to_sym but it's not working..
Thanks for the help!
Inside calculate_scoring you can use self to point to the record you are saving. So self._651_maturity = <some_value>, self[:_651_maturity] = <some_value> and self['_651_maturity'] are all valid methods to set _651_maturity.
Also, you can do something like:
my_attrib = '_651_maturity'
self[my_attrib] = 'foo'

Pointer on class object

In my Ruby model I want to apply default value on somes properties on my Recipe. So I added an before_save callback to apply it: This is my Recipe model:
class Recipe < ActiveRecord::Base
before_save :set_default_time
# other stuff
private
# set default time on t_baking, t_cooling, t_cooking, t_rest if not already set
def set_default_time
zero_time = Time.new 2000, 1 ,1,0,0,0
self.t_baking = zero_time unless self.t_baking.present?
self.t_cooling = zero_time unless self.t_cooling.present?
self.t_cooking = zero_time unless self.t_cooking.present?
self.t_rest = zero_time unless self.t_rest.present?
end
end
It's pretty work but I want to factorize it like this:
class Recipe < ActiveRecord::Base
before_save :set_default_time
# other stuff
private
# set default time on t_baking, t_cooling, t_cooking, t_rest if not already set
def set_default_time
zero_time = Time.new 2000, 1 ,1,0,0,0
[self.t_baking, self.t_cooling, self.t_cooking, self.t_rest].each{ |t_time|
t_time = zero_time unless t_time.present?
}
end
end
But it doesn't work. How can I loop on "pointer" on my object propertie?
it won't work because you refer strictly to value, thus your override doesn't work as expected. you may try this:
[:t_baking, :t_cooling, :t_cooking, :t_rest].each { |t_time|
self.send("#{t_time}=".to_sym, zero_time) unless self.send(t_time).present?
}

How can I iterate through a model then iterate again in my view?

I want to pull data for each of my users. I grab their person_id from my user table, then use each person's ID to figure out how many days each person has available, and show that in my view.
I'm not sure if I am doing this correctly because I am iterating in my controller then again in my view.
def how_many_days_users_have
#my_group = User.all.pluck(:person_id)
#my_group.each do |v|
#indirect_id_v = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:a_code).first
#v_range = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:ac).first
#v_range_taken = Empaccrl.where("person_id = ? and is_active = ?", '#{v]', 'Y').pluck(:taken).first
#total_v_hours = #v_range.to_d - #v_range_taken.to_d
#total_v_days = #total_v_hours / 8
end
Then in my view I use this to show me this data:
%tr.trace-table
-#indirect_id_v.each do |idd|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= idd
-#total_v_days.each do |days|
%tr.trace-table
%td.trace-table{:style => 'border: solid black;'}= days
Okay, first things first, move some of that junk to your model, like so:
class Empaccrl < ActiveRecord::Base
def self.all_people
where(person_id: User.all.pluck(:person_id))
end
def self.active_people
all_people.where(is_active: 'Y')
end
def self.active_vacation_data
active_people.select(:person_id, :ac, :taken)
end
def total_v_hours
ac.to_d - taken.to_d
end
def total_v_days
total_v_hours / 8
end
end
Then you can use:
peoples_vacation_information = Empaccrl.active_vacation_data.all
peoples_vacation_information.map do |person|
p "person #{person.person_id} has #{person.total_v_days} vacation days"
end
Honestly, you don't even need all that, but I'm not sure why you are doing what you are doing, so I figured better be safe and add stuff. Whatever you don't need, just ignore.

Relationships - accessing children elements

I'm trying to add a simple inventory management system. A class product has_many variants, and the variants belong_to a product and therefore have a product_id, along with a name and a quantity. When the user creates the product, I have the product generate 11 different variants (just with numerical values) by calling the following
Located in variants.rb (model)
def self.create_multiple_variants( product_id )
p = Product.find(product_id)
i = 11
while i <= 21
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
i += 1
end
end
Then when the user tries to show the page, the program will go through each variant belonging to the product and see if their is any quantity (which the admin adjusts along the way) like so:
Located in the view:
<div class="size"><br/>Size: <%= f.select(:size, #sizes_availiable, :prompt => "Select a Size...")
Located in product_controller:
#sizes_availiable = Variants.create_inventory_array( #product.id )
Located in variants.rb (model)
def self.create_inventory_array( product_id )
p = Product.find(product_id)
a = []
p.variants.each do |v|
a << variant.name if variant.qty > 0
end
a
end
I know with the naming it is a little confusing, as I am setting it up as something bigger but deprecating it for now, so sorry of thats a little confusing. For now you can think of variant as "size"
But the creation portion of it works fine, however when I go to show the product I get this message:
NameError in ProductController#show
app/models/variants.rb:20:in create_inventory_array'
app/controllers/product_controller.rb:18:inshow'
I assume that the way I am building the relationship is the source of the problem, either that or how I am calling it. Any ideas?
UPDATE:
I used the suggestions below, and it seems that now the problem lies in the second function. Here is my new variants.rb and the error I get:
class Variants < ActiveRecord::Base
attr_accessible :product_id, :name, :qty
belongs_to :product
def self.create_multiple_variants( product_id )
p = Product.find(product_id)
for i in 11..21
v = Variants.create
v.product = p
v.name = (i*2)
v.qty = 0
v.save!
end
end
def self.create_inventory_array( product_id )
p = Product.find(product_id)
a = []
p.variants.each do |variant|
a << variant.name if variant.qty > 0
end
a
end
end
NoMethodError in ProductController#create
undefined method `Variants' for #<Product:0x007fe9561ad550>
Application Trace | Framework Trace | Full Trace
app/models/variants.rb:8:in `block in create_multiple_variants'
app/models/variants.rb:7:in `each' app/models/variants.rb:7:in
`create_multiple_variants' app/controllers/product_controller.rb:33:in
`create
I still believe it's an issue with how the relationship is being build (I'm assigning variants.product = current_product, yet I call product.variants - I feel like the relationship is not being built both ways)
The problem is in this code:
p.variants.each do |v|
a << variant.name if variant.qty > 0
end
you pass in the variable v but refer to it as variant. To fix it change the line to
p.variants.each do |variant|
Also read this: http://guides.rubyonrails.org/active_record_querying.html#conditions you could make the code a lot more elegant by querying the variants for desired product_id and qty, and then calling map to get the names only.
Also this can be improved:
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
to
new_variant = p.variants.create name: "#{i*2}", qty: 0
Yes. You need to save the object.
To save it at the end of your loop:
new_variant.save!
Sidenote about this loop:
i = 11
while i <= 21
...
i += 1
end
This is a better way to write it because it's clearer:
for i in 11..21 do
...
end
And for blocks like this:
new_variant = Variants.create
new_variant.product = p
new_variant.name = (i*2)
new_variant.qty = 0
new_variant.save!
Make it easier to read:
v = Variants.create
v.product = p
v.name = i*2
v.qty = 0
v.save!
I figured out what was wrong - my model is Variants.rb (with an s) which at some point caused a problem. I renamed the file variants.rb as well as the class name Variants to variant.rb and Variant respectivly, restarted the server, and it worked! Thanks to those who helped!

Adding a bitwise virtual column to a model

I'm building this RoR site on an existing database. The user model on database has a column called "secret", which is a bitwise integer that holds information of the columns user has set as secret (first name, last name, etc).
Variables are to the power of two, for example: last name = 1<<1 = 2, first name = 1<<2 = 4, email == 1<<3 = 8, etc. So if user has set first name & email as secret, the column value becomes 4+8 = 12.
Now, I'm trying to find a generalized way to implement these virtual columns into a Rails model. So that, I could do (just a dummy example, the point being, i want to retrieve & store the status):
if user.secret_email?
user.secret_name_last = true
user.secret_name_first = false
end
How to implement these virtual columns neatly to a model (without modifying the existing database)? Current I've got following. It works, but it's not neat. As I've got 20 secret columns, the code looks very ugly.
SECRET_NAME_LAST = (1 << 1) # 2
attr_accessible :secret_name_last
def secret_name_last; secret & SECRET_NAME_LAST > 0 unless secret.nil?; end
def secret_name_last=(value); secret_set_value(SECRET_NAME_LAST, value); end
SECRET_NAME_FIRST = (1 << 2) # 4
attr_accessible :secret_name_first
def secret_name_first; secret & SECRET_NAME_FIRST > 0 unless secret.nil?; end
def secret_name_first=(value); secret_set_value(SECRET_NAME_FIRST, value); end
SECRET_EMAIL = (1 << 3) # 8
attr_accessible :secret_email
def secret_email; secret & SECRET_EMAIL > 0 unless secret.nil?; end
def secret_email=(value); secret_set_value(SECRET_EMAIL, value); end
***snip (17 more)***
private
def secret_set_value(item, value)
if self.secret.nil?
self.secret = 0
end
if value == "1" || value == true || value == 1
# Add item to secret column (if it doesn't exist)
if self.secret & item == 0
self.secret += item
end
else
# Remove item from secret column (if it exists)
if self.secret & item > 0
self.secret -= item
end
end
end
It would be great of I could just do something like:
as_bitwise :secret_name_first, :column=>'secret', :value=>4
as_bitwise :secret_name_last, :column=>'secret', :value=>2
Or even,
as_bitwise :secret, { :secret_name_last=>4, :secret_name_first=>2 }
EDIT
Based on Brandan's excellent answer, this is what I've got currently:
module BitwiseColumn
extend ActiveSupport::Concern
module ClassMethods
def bitwise_column(*args)
mapping = args.extract_options!
column_name = args.shift
real_column_name = args.shift
logger.debug "Initializing bitwisecolumn, column: " + column_name.to_s
mapping.each_pair do |attribute, offset|
logger.debug "\tSetting a pair: offset: " + offset.to_s + ", " + attribute.to_s
mask = 2 ** offset
class_eval %{
attr_accessible :#{column_name}_#{attribute}
def #{column_name}_#{attribute}?
#{real_column_name} & #{mask} > 0 unless #{real_column_name}.nil?
end
def #{column_name}_#{attribute}=(value)
if self.#{real_column_name}.nil?
self.#{real_column_name} = 0
end
if value == "1" || value == true || value == 1
if self.#{real_column_name} & #{mask} == 0
self.#{real_column_name} += #{mask}
end
else
if self.#{real_column_name} & #{mask} > 0
self.#{real_column_name} -= #{mask}
end
end
end
}
end
end
end
end
This allows me to use:
bitwise_column :secret, :realsecretcolumnatdatabase, :name_last=>1, :name_first=>2, :email=>3, :picture=>5, :dob=>6, :place=>12
After that, I can call User.first.secret_name_last? etc.
You can use class_eval to DRY up your code quite a bit. I'd also suggest factoring this behavior into some kind of a module separate from your User class so that you can test it thoroughly and separately from other User-specific behavior.
Like you, I tend to start these kinds of tasks with the desired API and work backwards. I started with this in my model:
class User < ActiveRecord::Base
include BitwiseColumn
bitwise_column :secret, :first_name => 1, :last_name => 2
end
The hash passed to bitwise_column maps the virtual attribute names to their mask value as an exponent. I felt like that was easier to manage than having to remember the powers of 2 myself :-)
Then I created the mixin:
module BitwiseColumn
extend ActiveSupport::Concern
module ClassMethods
def bitwise_column(*args)
mapping = args.extract_options!
column_name = args.shift
mapping.each_pair do |attribute, offset|
mask = 2 ** offset
class_eval %{
def secret_#{attribute}?
#{column_name} & #{mask} > 0 unless #{column_name}.nil?
end
def secret_#{attribute}=(value)
if self.#{column_name}.nil?
self.#{column_name} = 0
end
if value == "1" || value == true || value == 1
if self.#{column_name} & #{mask} == 0
self.#{column_name} += #{mask}
end
else
if self.#{column_name} & #{mask} > 0
self.#{column_name} -= #{mask}
end
end
end
}
end
end
end
end
This mixin creates two instance methods for each virtual attribute, one with a ? and one with a =, since that seems to be what you're after. I used your existing logic for the bitwise operations, which seems to work perfectly.

Resources