Rails - model attribute as a variable in function - ruby-on-rails

I think it is really basic question, but I cannot find solution.
I have simple help method:
def getProperNutrientValue(meal)
result = 0
if meal.ingredients.empty?
result
else
meal.ingredients.each do |i|
result += (i.quantity * #meal.products.find(i.product_id).calorific) / 100
end
result
end
end
Where "calorific" is an attribute in Product model.
class Product < ActiveRecord::Base
has_many :ingredients
has_many :meals, :through => :ingredients
validates :calorific, :numericality => true, :allow_nil => true
validates :water, :numericality => true, :allow_nil => true
I want to DRY this function, and set attribute as a variable. Then I will be can use this function for example for water attribute.
So I want to achieve something like that:
def getProperNutrientValue(meal, attribute)
result = 0
if meal.ingredients.empty?
result
else
meal.ingredients.each do |i|
result += (i.quantity * #meal.products.find(i.product_id).attribute) / 100
end
result
end
end
But of course it doesn't work... How can I fix it?

You can use send(method_name) method. I don't understand the logic behind using #meal variable. Either way there are some options to improve your code, for example:
def getProperNutrientValue(meal, attribute)
result = 0
meal.ingredients.each do |i|
result += (i.quantity * #meal.products.find(i.product_id).send(attribute).to_i) / 100
end
result
end
getProperNutrientValue(meal, :calorific)

Related

Passing arguments to Faker for my method in Rails?

I have a model called Booking, that should calculate the total from several numbers (amount, deposit, and fee are all added together). I'm having trouble getting these arguments to be seen in Faker.
it "should calculate the total" do
myvar = FactoryGirl.create(:booking, :amount => 900, :deposit => 20, :fee => 8)
myvar.totalamount.should == 928
end
And here's my method:
class Booking < ActiveRecord::Base
validates :to, :from, :amount, presence: true
def totalamount(amount,deposit,fee)
total = (amount + deposit + fee)
return total
end
end
The error message: "wrong number of arguments (0 for 3)"
However, when I do a puts myvar.deposit, it returns the value I gave it - 20.
What am I doing wrong?
Edit: Here is my Factory build for Booking:
FactoryGirl.define do
factory :booking do |b|
b.from { Faker::Lorem.sentence(word_count=3) }
b.to { Faker::Lorem.sentence(word_count=3) }
b.amount { Faker::Number.digit }
end
end
class Booking < ActiveRecord::Base
validates :to, :from, :amount, presence: true
def totalamount
total = (amount + deposit + fee)
return total
end
end
Just had to remove the 3 required attributes after 'totalamount'.

How to add in controller in Rails?

How exactly do you do arithmetic operations in the controller?
I've tried this
def choose
rand_id = rand(Gif.count)
#gif1 = Gif.first(:conditions => [ "id >= ?", rand_id])
#gif2 = Gif.first(:conditions => [ "id >= ?", rand_id])
if #gif1.id == #gif2.id
#gif2 = Gif.first(:order => 'Random()')
end
total = #gif1.votes+#gif2.votes
number_one = #gif1.votes/total*100
number_two = #gif2.votes/total*100
#gif1.update_attribute(:votes, number_one)
#gif2.update_attribute(:votes, number_two)
end
class Gif < ActiveRecord::Base
before_save :default_agree_count
def default_agree_count
self.agree = 1
self.votes = 1
end
VALID_REGEX = /http:\/\/[\S]*\.gif$/
attr_accessible :link, :votes, :agree
acts_as_votable
validates :link, presence: true, format: {with: VALID_REGEX}, uniqueness: {case_sensitive: false}
end
However, it says that +, /, * are all unknown operators. I've also tried doing them within like such #gif1.agree = '#gif1.votes+1' with and without '. Any ideas?
Thanks!
I suppose you are using Acts As Votable gem.
Basically it works as follows:
#post = Post.new(:name => 'my post!')
#post.save
#post.liked_by #user
#post.votes.size # => 1
So try replacing .votes with .votes.size in your code.
E.g.:
total = #gif1.votes.size + #gif2.votes.size
Further to #ilyai's answer (which I +1'd) (I don't have much experience with the Acts As Votable gem), you can perform any calculations you want in your controllers
Here's some refactoring for you:
.first
def choose
Gif.update_votes
end
class Gif < ActiveRecord::Base
before_save :default_agree_count
def default_agree_count
self.agree = 1
self.votes = 1
end
def self.update_votes
rand_id = rand count #-> self.count?
gif = where("id >= ?", rand_id)
gif1 = gif[0]
gif2 = gif[1]
if gif1.id == gif2.id
gif2 = where(order: 'Random()').first
end
total = (gif1.votes) + (gif2.votes)
number_one = ((gif1.votes /total) * 100)
number_two = ((gif2.votes / total) * 100)
gif1.update_attribute(:votes, number_one)
gif2.update_attribute(:votes, number_two)
end
VALID_REGEX = /http:\/\/[\S]*\.gif$/
attr_accessible :link, :votes, :agree
acts_as_votable
validates :link, presence: true, format: {with: VALID_REGEX}, uniqueness: {case_sensitive: false}
end

Raising errors in attribute accessor overrides?

I am overriding an attribute accessor in ActiveRecord to convert a string in the format "hh:mm:ss" into seconds. Here is my code:
class Call < ActiveRecord::Base
attr_accessible :duration
def duration=(val)
begin
result = val.to_s.split(/:/)
.map { |t| Integer(t) }
.reverse
.zip([60**0, 60**1, 60**2])
.map { |i,j| i*j }
.inject(:+)
rescue ArgumentError
#TODO: How can I correctly report this error?
errors.add(:duration, "Duration #{val} is not valid.")
end
write_attribute(:duration, result)
end
validates :duration, :presence => true,
:numericality => { :greater_than_or_equal_to => 0 }
validate :duration_string_valid
def duration_string_valid
if !duration.is_valid? and duration_before_type_cast
errors.add(:duration, "Duration #{duration_before_type_cast} is not valid.")
end
end
end
I am trying to meaningfully report on this error during validation. The first two ideas that I have had are included in the code sample.
Adding to errors inside of the accessor override - works but I am not certain if it is a nice solution.
Using the validation method duration_string_valid. Check if the other validations failed and report on duration_before_type_cast. In this scenario duration.is_valid? is not a valid method and I am not certain how I can check that duration has passed the other validations.
I could set a instance variable inside of duration=(val) and report on it inside duration_string_valid.
I would love some feedback as to whether this is a good way to go about this operation, and how I could improve the error reporting.
First, clean up your code. Move string to duration converter to the service layer. Inside the lib/ directory create StringToDurationConverter:
# lib/string_to_duration_converter.rb
class StringToDurationConverter
class << self
def convert(value)
value.to_s.split(/:/)
.map { |t| Integer(t) }
.reverse
.zip([60**0, 60**1, 60**2])
.map { |i,j| i*j }
.inject(:+)
end
end
end
Second, add custom DurationValidator validator
# lib/duration_validator.rb
class DurationValidator < ActiveModel::EachValidator
# implement the method called during validation
def validate_each(record, attribute, value)
begin
StringToDurationConverter.convert(value)
resque ArgumentError
record.errors[attribute] << 'is not valid.'
end
end
end
And your model will be looking something like this:
class Call < ActiveRecord::Base
attr_accessible :duration
validates :duration, :presence => true,
:numericality => { :greater_than_or_equal_to => 0 },
:duration => true
def duration=(value)
result = StringToDurationConverter.convert(value)
write_attribute(:duration, result)
end
end

Ruby on Rails: Is it right how I coded my Category-Tree (Is it ruby-like?)

I am new to Rails and just got my category tree working. Now I am not sure if what I have done is "ruby conform" or "ruby-like". I come from PHP and have to change some of my habits but this is not easy. I just want to check if I am on the right way.
The structure is done by the scaffold-command so I guess its correct.
So obvisoulythere is a model-class which is called Category and inherits from ActiveRecord::Base. This model has the following attributes/database fields:
*id,media,image,small_image,clicks,parent,active,description,name,created_at,updated_at*
This is the content of my model-class:
class Category < ActiveRecord::Base
has_many :articles,
:foreign_key => 'category'
belongs_to :parent_object,
:foreign_key => "parent",
:class_name => "Category"
has_many :children,
:foreign_key => "parent",
:class_name => "Category",
:order => "name ASC",
:dependent => :delete_all
#tree = Hash.new
#treepart = Hash.new
def self.category_tree
#root_categories = self.find(:all, :conditions => ["parent = ?", 0])
if #root_categories.length >= 1
#root_categories.each do |level|
#tree[level.id] = level.child_loop(level)
end
#tree
end
end
def child_loop(child)
#treepart = { :category => child }
#treepart[:children] = Hash.new
child.children.each do |child|
#treepart[:children][child.id] = child.child_loop(child)
end
#treepart
end
end
Categories can be nested therefore I have integrated a self-relating belongs_to and has_many function. I have called the parent *:parent_object* because only :parent does not work. Maybe it is in conflict with the attribute-name.
In the model I collect all categories with the method *category_tree* and *child_loop*. After this call I get an image of the category-tree in form of a hash.
Category.category_tree
I do this directly in the Articles´ *_form.html.erb* and pass it to my helper which is generating the html. Here is the call from the from-template:
<%= build_category_tree(Category.category_tree).html_safe %>
The helper is rendering as follows:
module CategoriesHelper
def build_category_tree(object_tree)
tree = object_tree
#treestring = "<ul>" + self.level_loop(tree) + "</ul>"
end
def level_loop(level)
#levelstring = ''
if !level.nil?
level.each do |id,item|
if item.has_key?(:category) && !item.nil?
#levelstring += "<li>" + item[:category].name + "</li>"
#levelstring += "<ul>" + self.level_loop(item[:children]) + "</ul>"
end
end
end
# in the end, return string to prevent a nil return
#levelstring += ""
end
end
Is this the ruby-way to code, can I shorten or change something completely?
Thanks for your help

Ruby complex validation

Have a product that belongs to a category. Want to create a promotion for a short period of time (lets say a week or two), but their can be only one promotion per category during that time.
How can I create a custom validation for this?
product class
belongs_to :categories
name:string
desc:text
reg_price:decimal
category_id:integer
promo_active:boolean
promo_price:decimal
promo_start:datetime
promo_end:datetime
end
category class
has_many :products
name:string
end
Update to possible solution???
class Product < ActiveRecord::Base
attr_accessible :name, :desc, :reg_price, :category_id, :promo_active, :promo_price, :promo_start, :promo_end
belongs_to :category
#validate :check_unique_promo
#Tweaked original to be more exact and
#Give clue if its the start or end date with the error.
validate :check_unique_promo_start
validate :check_unique_promo_end
def check_unique_promo
errors.add_to_base("Only 1 promo allowed") unless Product.count(:conditions => ["promo_active = ? AND promo_end < ?", true, self.promo_start]) == 0
end
def check_unique_promo_start
errors.add_to_base("Start date overlaps with another promotion.") unless self.promo_active == false || Product.count(:conditions => ['promo_end BETWEEN ? AND ? AND category_id = ? AND promo_active = ? AND id != ?',self.promo_start, self.promo_end, self.category_id, true, self.id]) == 0
end
def check_unique_promo_end
errors.add_to_base("End date overlaps with another promotion.") unless self.promo_active == false || Product.count(:conditions => ['promo_start BETWEEN ? AND ? AND category_id = ? AND promo_active = ? AND id != ?',self.promo_start, self.promo_end, self.category_id, true, self.id]) == 0
end
end
I Skip self if promo_active false for performance.
I would use the validates_uniqueness_of validation so:
class Product < ActiveRecord::Base
belongs_to :categories
validates_uniqueness_of :promo_active, :scope => :category_id, :allow_nil => true
before_save :update_promos
private
def update_promos
# custom code to set :promo_active to nil if the promo is
# not active and to something else if it is active
end
end
Take 2:
validate :check_unique_promo
def check_unique_promo
errors.add_to_base("Only 1 promo allowed") unless Product.count(:conditions => ["active_promo = 1 AND promo_end < ?", self.promo_start]) == 0
end

Resources