Im trying to refactor my code in the Team model. Teams has:many Projections, and Projections has stats for a player (goals, assists, hits etc). I want to implement one method that can tally a specific total if you pass it the parameter you are looking for.
Something like this, where I could call:
example_team.total(goals)
or
example_team.total(assists)
class Team < ApplicationRecord
# def total(value)
# total = 0
# self.projections.each do |projection|
# total += projection.value
# end
# return total
# end
The issue im having is that im essentially trying to pass a method name within a different method as a parameter. Im getting errors when im trying to do this. How can I get this refactoring to work???
This is my current unfactored code:
class Team < ApplicationRecord
def goals
total = 0
self.projections.each do |projection|
total += projection.goals
end
return total
end
def assists
total = 0
self.projections.each do |projection|
total += projection.assists
end
return total
end
def pp_points
total = 0
self.projections.each do |projection|
total += projection.pp_points
end
return total
end
def hits
total = 0
self.projections.each do |projection|
total += projection.hits
end
return total
end
def blocks
total = 0
self.projections.each do |projection|
total += projection.blocks
end
return total
end
To send a method dynamically you can use public_send.
class Team < ApplicationRecord
def total(field)
total = 0
self.projections.each do |projection|
total += projection.public_send(field)
end
return total
end
end
To simplify the example we can use sum that takes a symbol as an argument.
class Team < ApplicationRecord
def total(field)
projections.sum(field)
end
end
To make it work you should use something like this example_team.total(:goals).
Related
When I create an article I set a quantity.
Each time an article is sold, its quantity is decreased by a method.
I need to keep my initial stock, the number of sales and the remaining stock...
So my question is:
How can I keep the initial quantity, and the quantity I could add on update?
# article.rb
before_create :quantity_on_create
before_update :quantity_on_update
def quantity_on_create
self.quantity
end
def quantity_on_update
quantity_on_create += self.quantity
end
quantity_on_create remains nil ?
Maybe you could consider an additional attribute for quantity_stock
# article.rb
before_create :quantity_on_create
before_update :quantity_on_update
def quantity_on_create
self.quantity_stock = self.quantity = 10 # initial value
end
def quantity_on_update
if self.quantity > 0
self.quantity -= 1
else
errors.add(:base, 'out of stock')
throw(:abort)
end
end
So I have a User model and a Post model
Post belongs to a User
Post has a field in the database called score
In the Post model I have a method called score which gives the post a score based on the fields (needs to be done this way):
def score
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
score
end
The Question:
There are loads of Users and loads of Posts. So What I'm trying to do is after the score is worked out, I want to save it to the Post score field in the database for each Post. The score should be updated if the user updates the Post.
I have looked at using after_update :score! but don't understand how to apply the logic
It looks a little like you are trying to re-invent the wheel ActiveRecord provides you.
If you have a database field score, then ActiveRecord will automatically provide an attribute_reader and attribute_writer for score and you should not override these unless you have a really really good reason for it, e.g. you need to add some other resources or some serious business logic into it.
There is a way easier way to solve it, by using the before_save hook, which will kick in before any #create or #update:
class Post
attribute_accessible :score # if you have Rails 4.x you can omit this line
before_save :update_score
private
def update_score
new_score = 0
self.score = [:title, :author, :body].each do |field|
new_score += 5 if send(field).present?
end
self.score = new_score
end
This way, ActiveRecord will handle the saving for you and your score will always up to date. Additionally Post#score will always return the real value currently saved in the database
You can do it like this
after_update :score!
def score!
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
self.update_column(:score, score)
end
This is to be done in your Post model.
You can do it using update_column method. Like:
def score
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
self.update_column(:score, score)
end
You need to override the setter method in the Post model
attr_accessible :score
def score=(value)
score = 0
if self.title.present?
score += 5
end
if self.author.present?
score += 5
end
if self.body.present?
score += 5
end
write_attribute(:score, score)
end
I understand what Ruby self means, and I was trying to solve certain challenges on Tealeaf: http://www.gotealeaf.com/books/oo_workbook/read/intermediate_quiz_1
Here is the actual problem:
Snippet 1:
class BankAccount
def initialize(starting_balance)
#balance = starting_balance
end
# balance method can be replaced with attr_reader :balance
def balance
#balance
end
def positive_balance?
balance >= 0 #calls the balance getter instance level method (defined above)
end
end
Now for Snippet 1, running this code:
bank_account = BankAccount.new(3000)
puts bank_account.positive_balance?
prints true on the console, whereas for snippet 2:
Snippet 2:
class InvoiceEntry
attr_reader :product_name
def initialize(product_name, number_purchased)
#quantity = number_purchased
#product_name = product_name
end
# these are attr_accessor :quantity methods
# quantity method can be replaced for attr_reader :quantity
def quantity
#quantity
end
# quantity=(q) method can be replaced for attr_writer :quantity
def quantity=(q)
#quantity = q
end
def update_quantity(updated_count)
# quantity=(q) method doesn't get called
quantity = updated_count if updated_count >= 0
end
end
Now for snippet 2, on running this code:
ie = InvoiceEntry.new('Water Bottle', 2)
ie.update_quantity(20)
puts ie.quantity #> returns 2
Why is this not updating the value?
Why is it working for the first case while not for the second?
You are assigning to quantity the local variable.
If you want to assign to the instance variable (via your def quantity= function) you need to do
self.quantity = updated_count if updated_count >= 0
Essentially, you're making a function call (quantity=) on self.
In snippet 1, balance is a pure function call because there is no assignment going on.
I have an "attack" method that for some reason, returns ArgumentError: wrong number of arguments (0 for 1), even though I provide an argument. The call is a.attack(b) where both a and b are instances of the UnitInstance class
This is my class and the attack method is at the botton - any suggestions are much appreciated!
EDIT: Also, this is a model within a rails application, but it doesn't need to connect with he DB
class UnitInstance
attr_accessor :name, :health, :current_health, :attack, :defence,
:initiative, :amount, :conditions
def initialize(unit_id, amount)
unit = Unit.find(unit_id)
#name = unit.name
#health = unit.health
#current_health = unit.health
#attack = unit.attack
#defence = unit.defence
#initiative = unit.initiative
#amount = amount
#conditions = []
end
def strength
amount * attack
end
def dead?
health <= 0 and amount <= 0
end
def find_target(enemies)
end
def decrease_health(_amount)
hp = health * (self.amount - 1) + current_health
hp -= _amount
self.amount = hp / health + 1
if hp % health == 0
self.amount -= 1
self.current_health = 5
else
self.current_health = hp % health
end
end
def attack(target)
target.decrease_health(strength)
decrease_health(target.defence) unless target.dead?
"#{name.titleize} attacked #{target.name.titleize} for #{attack} damage,"
end
end
You have a method named attack with one argument which you call by a.attack(b)
def attack(target)
target.decrease_health(strength) ## <<-- Notice this
decrease_health(target.defence) unless target.dead?
"#{name.titleize} attacked #{target.name.titleize} for #{attack} damage,"
end
You are invoking strength method inside attack and within strength
def strength
amount * attack ## << -- No arguments here
end
You are getting error from strength method where you are invoking attack without any arguments.
#amount and #attack are instance variables for which you have defined getter and setter methods using attr_accessor.
So, for #attack you now have two methods, attack and attack=.
Remember that
There may be only one method with given name in Ruby class. If several
methods with the same name defined in the class - the latest
overwrites previous definitions.
So, when you define attack(target) with argument the accessor method attack is overwritten. Now, you are left with only two methods attack(target) and attack=
I would like to sort my links using an algorithm, all the information needed can be derived from values in my Links table. How can I return items sorted by the item's calculated Score?
Schema https://gist.github.com/1326044
Index Action https://gist.github.com/1326045
Time Calculation https://gist.github.com/1326050
Algorithm
# Score = (P-1) / (T+2)^G
# P = points of an item (and -1 is to negate submitters vote)
# T = time since submission (in hours)
# G = Gravity, defaults to 1.8
I'd do as follows:
First, in your model:
after_initialize :calculate_score
attr_accessor :score
def calculate_score
unless self.new_record?
time_elapsed = (Time.zone.now - self.created_at) / 3600
self.score = (self.points-1) / (time_elapsed+2)^G # I don't know how you retrieve G
end
end
In your controller:
#links = Link.all.sort_by(&:score)