Why is-a-role-of is one of the inheritance mistakes? - oop

The illustration below is taken from my lecture slide. I've use both composition and inheritance to code a Student - Person class, I think both of them pretty reasonable.
(I know if One person can have many positions - 1:n , then inheritance doesn't work though, so I'm only taking about 1 person : 2 position relationship).
The code using Inheritance:
class Person
attr_accessor :name, :gender, :height, :weight
def initialize(name, gender, height, weight)
#name = name
#gender = gender
#height = height
#weight = weight
end
def cry
"woooo"
end
end
class Student < Person
attr_accessor :student_id
def initialize(name, gender, height, weight, student_id)
super(name, gender, height, weight)
#student_id = student_id
end
def study
"Student #{name} with #{student_id} is studying now"
end
end
s = Student.new("Lin", "Male", 173, 75, 666777)
puts s.cry()
puts s.study
Code using Composition:
class Person
attr_accessor :name, :gender, :height, :weight, :position
def initialize(name, gender, height, weight, position = nil)
#name = name
#gender = gender
#height = height
#weight = weight
#position = position
end
def cry
"woooo"
end
end
class Student
attr_accessor :student_id
def initialize(student_id)
#student_id = student_id
end
def study
"#{student_id} is studying now"
end
end
s = Student.new(666777)
p = Person.new("Lin", "Male", 173, 75, s)
puts p.cry()
puts p.position.study
And I found the code using composition has one bad side, I can't make student call he's name. I mean I can't make the study() method return something like the inheritance code does:
"Student #{name} with #{student_id} is studying now"

Related

Rails add divide calculation to existing query

I have a query in which I pull all users who have invested in the portfolio, like below:
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
end
def call
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select('users.*, SUM(cash_transactions.shares_number) as total_shares_number')
.group('users.id')
end
attr_reader :portfolio
end
Is it possible to add inside of above query a line that will do the division of two numbers as below?
(user.total_shares_number / portfolio.portfolio_setup_infos.last.total_shares_sold)
# total_shares_number comes from first select
# portfolio_setup_infos is a has_many relation to portfolio
[Edit]
#models association
class User < ApplicationRecord
has_one :wallet, as: :walletable
has_many :cash_transactions, through: :wallet
end
class Portfolio < ApplicationRecord
has_one :wallet, as: :walletable
end
class Wallet < ApplicationRecord
belongs_to :walletable, polymorphic: true
has_many :cash_transactions
end
class CashTransaction < ApplicationRecord
belongs_to :wallet
belongs_to :to_wallet, class_name: 'Wallet', optional: true
end
[Edit 2]
> divisor = portfolio.portfolio_setup_infos.last.total_shares_sold
PortfolioSetupInfo Load (0.5ms) SELECT "portfolio_setup_infos".* FROM "portfolio_setup_infos" WHERE "portfolio_setup_infos"."portfolio_id" = $1 ORDER BY "portfolio_setup_infos"."id" DESC LIMIT $2 [["portfolio_id", 6], ["LIMIT", 1]]
=> 263
> shareholders = User.joins(:cash_transactions).where(cash_transactions: { to_wallet: portfolio.wallet }).select("users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/#{divisor}").group('users.id')
Wallet Load (0.6ms) SELECT "wallets".* FROM "wallets" INNER JOIN "spv_setups" ON "wallets"."walletable_id" = "spv_setups"."id" WHERE "spv_setups"."portfolio_id" = $1 AND "wallets"."walletable_type" = $2 LIMIT $3 [["portfolio_id", 6], ["walletable_type", "SpvSetup"], ["LIMIT", 1]]
User Load (3.3ms) SELECT users.*, SUM(cash_transactions.shares_number) as total_shares_number, (total_shares_number/263 FROM "users" INNER JOIN "wallets" ON "wallets"."walletable_type" = $1 AND "wallets"."walletable_id" = "users"."id" INNER JOIN "cash_transactions" ON "cash_transactions"."wallet_id" = "wallets"."id" WHERE "cash_transactions"."to_wallet_id" = $2 GROUP BY "users"."id" /* loading for inspect */ LIMIT $3 [["walletable_type", "User"], ["to_wallet_id", 8], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "FROM")
LINE 1: ... as total_shares_number, (total_shares_number/263 FROM "user...
^
class Portfolio < ApplicationRecord
def divisor
portfolio_setup_infos.last.total_shares_sold
end
end
class PortfolioShareholdersQuery
def initialize(portfolio)
#portfolio = portfolio
#divisor = portfolio.divisor.to_f
end
def call
select =<<-SEL.squish
users.*,
SUM(cash_transactions.shares_number) as total_shares_number,
(SUM(cash_transactions.shares_number)/#{divisor}) as ratio
SEL
User.joins(:cash_transactions)
.where(cash_transactions: { to_wallet: portfolio.wallet })
.select(select)
.group('users.id')
end
attr_reader :portfolio, :divisor
end

Need help iterating through a nested list but

'''
patients = [[175.8, 73.4], [180.2, 59.5], [165.4, 70.2], [193.5, 120]]
def calculate_bmi(height, weight):
return weight / ((height / 100 )**2)
def get_bmi_category(bmi):
if bmi < 18.5:
return "underweight"
elif bmi < 25.0:
return "normal weight"
elif bmi < 30:
return "overweighting"
else:
return "obesity"
for patient in patients:
height, weight = patients[0]
bmi = calculate_bmi(height, weight)
bmi_category = get_bmi_category(bmi)
print("Patient's BMI is: {} ({})".format(bmi, bmi_category))
'''
When I print, I only get the results of the first nested list four times, when I want the results of all the nested loops. What else can i do?
problem is with the following line of code
height, weight = patients[0]
above would only assign values of [175.8, 73.4] to height and weight.
update it as follows
height, weight = patient
or
for height, weight in patients:
#in this case you'll have to remove, height, weight = patients[0]
Change height, weight = patients[0] to height, weight = patient.

Professor's answer to Python homework will not run: TypeError: __init__() missing 1 required positional argument: 'gender'

I'm sure this has been asked before, but at I'm not at the level where I can understand how the other question answers mine just yet. Can someone explain the problem with this code? I cannot see it:
class Person:
name = ''
age = 0
gender = ''
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Employee(Person):
title = ''
salary = 0
def __init__(self, name, age, gender, title, salary):
Person.__init__(name, age, gender)
self.title = title
self.salary = salary
George = Employee("George", 30, "Male", "Manager", 50000)
print("George's info is: Name is %s, Age is %d, Gender is %s, Title is %s, and Salary is %d" % (
George.name, George.age, George.gender, George.title, George.salary))
The error returned in PyCharm is:
Traceback (most recent call last):
File "/Users/Sam/PycharmProjects/HW7/HW7-q2_Profs-Solution.py", line 22, in <module>
George = Employee("George", 30, "Male", "Manager", 50000)
File "/Users/DrewAndMon/PycharmProjects/HW7/HW7-q2_Profs-Solution.py", line 17, in __init__
Person.__init__(name, age, gender)
TypeError: __init__() missing 1 required positional argument: 'gender'
Process finished with exit code 1
Change this line:
Person.__init__(name, age, gender)
to:
Person.__init__(self, name, age, gender)
Explanation: the Person.__init__ method takes four parameters, the first one of which is the self parameter for the object being initialized. Normally, the self parameter is special in that it automatically takes the value of the object that the method is called on. However, since Person is a class, not an object, in this case the method is not being called on an object, so self parameter must be passed as an explicit argument.
It is more normal to write this instead as:
super().__init__(name, age, gender)
This looks a bit stranger, but it avoids unnecessarily writing the name of the parent class a second time, and it works correctly when a class has two superclasses.
What needs to be changed is the call to the Person.__init__(name, age, gender) in class Employee, in definition it is def __init__(self, name, age, gender):
Your correct code is-
class Person:
name = ''
age = 0
gender = ''
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Employee(Person):
title = ''
salary = 0
def __init__(self, name, age, gender, title, salary):
Person.__init__(self, name, age, gender)
self.title = title
self.salary = salary
George = Employee("George", 30, "Male", "Manager", 50000)
print("George's info is: Name is %s, Age is %d, Gender is %s, Title is %s, and Salary is %d" % (
George.name, George.age, George.gender, George.title, George.salary))
And this will give you your output as-
George's info is: Name is George, Age is 30, Gender is Male, Title is Manager, and Salary is 50000

Polymorphism OOP : Instance of 'child' has no 'basic_attack' memberpylint (no-member)

I'm trying to understand Polymorphism in OOP, i've built a monster parent class, from this main class you can inherit oarcs, wizards and giants..but the main attack method is coded in the parent monster class, and have only a string that is put in a variable name basic_attack
The problem is when i call this attack variable in oarcs, wizards or giants in the child classes, i get this error Instance of 'oarc' has no 'basic_attack' memberpylint(no-member)
please go easy on me and feel free to review my code below.
class monster:
def __init__(self, damage, speed, skill):
self.damage = damage
self.speed = speed
self.skill = skill
def attack(self):
basic_attack = "you've been attacked by "
return basic_attack
class oarc(monster):
def __init__(self, damage, speed, skill, oarc_data):
super().__init__(damage, speed, skill)
self.oarc_data = oarc_data
def attack(self):
return self.basic_attack + self.oarc_data
class wizard(monster):
def __init__(self, damage, speed, skill, wizard_data):
super().__init__(damage, speed, skill)
self.wizard_data = wizard_data
def attack(self):
return self.basic_attack + self.wizard_data
class giant(monster):
def __init__(self, damage, speed, skill, giant_data):
super().__init__(damage, speed, skill)
self.giant_data = giant_data
def attack(self):
return self.basic_attack + self.giant_data
oarc_object = oarc(56, 150, 'ugly', 'Oarc')
wizard_object = wizard(20, 100, 'fast', 'Wizard')
giant_object = giant(100, 20, 'strong', 'Giant')
list = [oarc_object, wizard_object, giant_object]
for each in list:
print(each.attack)
print(oarc_object.attack())
Ok, basically looks like self.basic_attack is never set. Could probably fix with self.basic_attack = basic_attack in def __init__() of class Monster.
The basic_attack = "you've been attacked by " under def attack() in class Monster is not the same as setting basic_attack. That basic_attack is constrained to the attack() function.
For example, let's abstract at non-OOP code;
# define a
a = 0
def test():
# define out
a = 2
# output out
return a
print(test())
>> 2
print(a)
>> 0
Now, if we did not redefine a in test():
# set a
a = 0
# define test
def test():
return a
print(test())
>> 0
print(a)
>> 0
Now if a was only defined within test();
# define test
def test():
# set a
a = 2
# output
return a
print(test())
>> 2
print(a)
>> NameError: name 'a' is not defined
Here's a short Intro to OOP in Python;
Quick & easy; Object-oriented Programming from the ground up with Examples from Pikachu and Bay Area Rapid Transit (BART)
And here's a bit more in-depth application of OOP to post to LinkedIn;
LinkedOut

Custom Validation in Model with Parameter from controller

i have this chunk of codes in my controller
def create
number = params[:number]
#color = Colors.new(params[:colors])
#color.save
end
and i have this validation in model color.rb
validate :should_be_primary
def should_be_primary
#validations here
end
I just want the validation should only run when my params[:number] == 1
Note: params[:number] is only a parameter and not a table field.
anyone please help me.
def create
number = params[:number]
params[:colors][:param_number] = number
#color = Colors.new(params[:colors])
#color.save
end
validate :should_be_primary
def param_number=(number)
#number = number
end
def should_be_primary
if #number
#blah blah
end
end
Try this on your model.rb
validate :should_be_pimary if self.number == 1
def should_be_primary
#validations here
end