I get the error "NoMethodError: undefined method `<<' for nil:NilClass" when trying to add an object to an empty array. I think it relates to the array being nil instead of empty, and it's not allowing me to append a new object.
The problem occurs with the last line cashier.rule_set.add(apple_rule). Not sure if I am implementing the RuleSet class and initializing #rules correctly.
class Rule
attr_reader :sku, :quantity, :price
def initialize(sku, quantity, price)
#sku = sku
#quantity = quantity
#price = price
end
end
class RuleSet
attr_accessor :rules
def initalize()
#rules = []
end
def add(rule)
#rules << rule
end
def rule_for_sku(sku)
#rules.detect { |r| r.sku == sku }
end
end
class Product
attr_accessor :name, :price, :sku
def initialize(name, price)
puts "Added #{name}, which costs $#{price} to available inventory."
#name = name
#price = price
#sku = (rand(100000) + 10000).to_s
end
end
class Cashier
attr_accessor :rule_set
def initialize
#cart = []
#total_cost = 0
#rule_set = RuleSet.new
end
def add_to_cart(product)
puts "Added #{product.name} to your cart."
#cart << product
end
def in_cart
#cart.each_with_object(Hash.new(0)) {|item, counts| counts[item] += 1}
end
def checkout
self.in_cart.each do |item, quantity|
rule = self.rule_set.rule_for_sku(item.sku)
if rule.present? && quantity >= rule.quantity
total_cost += item.price
end
end
end
end
##Testing
#Initialize list of available products and costs
apple = Product.new("apple", 5)
banana = Product.new("banana", 2)
grape = Product.new("grape", 3)
apple_rule = Rule.new(apple.sku, 3, 12)
cashier = Cashier.new
cashier.rule_set.add(apple_rule)
You have misspelt initialize in your RuleSet class (initalize) so that method isn't being called and #rules is not being set to an empty array.
Related
I built a working shopping cart system the other day using Ruby on Rails, and with the guidance of following a tutorial. Now I would like to modify the existing shopping cart item to incorporate the size of a particular item being placed in the cart.
I created a migration file to add a size column to the Products table, and then I started modifying CartItem class, and the Cart class files respectively.
class CartItem
attr_reader :product_id, :quantity, :size
def initialize product_id, quantity = 1, size
#product_id = product_id
#quantity = quantity
#size = size
end
def increment
#quantity = #quantity + 1
end
def product
Product.find product_id
end
def total_price
# puts "Hello cart_item"
product.price * quantity
end
end
class Cart
attr_reader :items
def self.build_from_hash hash
items = if hash["cart"] then
hash["cart"]["items"].map do |item_data|
CartItem.new item_data["product_id"], item_data["quantity"], item_data["size"]
end
else
[]
end
new items
end
def initialize items = []
#items = items
end
def add_item product_id, size
item = #items.find { |item| item.product_id == product_id
item.size == size }
if item
item.increment
else
#items << CartItem.new(product_id, size)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def serialize
items = #items.map do |item|
{
"product_id" => item.product_id,
"quantity" => item.quantity,
"size" => item.size
}
end
{
"items" => items
}
end
def total_price(shipping_price = 0)
# puts "Hello cart"
#items.inject(0) { |sum, item| sum + item.total_price } + shipping_price
end
end
However, I'm getting the following error,
Because your add_item method should be into two parameter, but your params is hash can't use params[:id, :size] replace params[:id], params[:size] it work.
Is there a clean way to destroy all children NOT included in array of passed nested attributes?
Now I have to find difference between actual children and nested attributes array, and then set _destroy: true for each, but it looks ugly.
class Report < ActiveRecord::Base
has_many :consumed_products
accepts_nested_attributes_for :consumed_products, allow_destroy: true
def nested_attributes_destroy_difference(attrs)
combined = attrs.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}
diff = consumed_products - consumed_products.where(combined)
attrs + diff.map{|i| {id: i.id, _destroy: true} }
end
end
class Api::V2::ReportsController < Api::V2::BaseController
def update
report = Report.find(params[:id])
report_attributes = report_params
if params[:consumed_products]
report_attributes.merge!(consumed_products_attributes: report.nested_attributes_destroy_difference(consumed_products_attributes))
end
report.assign_attributes report_attributes
end
private
def consumed_products_attributes
params[:consumed_products].map do |p|
{product_id: p[:id], product_measure_id: p[:measure_id], quantity: p[:quantity]}
end
end
def report_params
#...
end
end
I have the following parameters
def note_params
params.require(:note).permit(
:content
)
end
Now i am trying to check of the content was empty for :content i am passing this to a service object
def add_note_to_plan
unless #note_params.content.empty?
puts "======================================================"
note = Note.new(
note_params.merge(
plan: #plan,
user: #current_user
)
)
note.save
end
puts "=================== outside ==================================="
end
Service Object
class PlanCreator
def initialize(current_user, venue_params, plan_params, note_params)
#current_user = current_user
#venue_params = venue_params
#plan_params = plan_params
#note_params = note_params
end
attr_reader :venue, :plan
def create
#venue = new_or_existing_venue
#plan = new_or_existing_plan
save_venue && save_plan && add_current_user_to_plan && add_note_to_plan
end
def errors
{
venue: venue_errors,
plan: plan_errors,
note: note_errors
}
end
private
attr_reader :current_user, :venue_params, :plan_params, :note_params
..... Removed all the unnecessary methods
def add_note_to_plan
unless #note_params.content.empty?
puts "======================================================"
note = Note.new(
note_params.merge(
plan: #plan,
user: #current_user
)
)
note.save
end
puts "=================== outside ==================================="
end
end
Error:
NoMethodError - undefined method `content' for
{"content"=>""}:ActionController::Parameters:
Change this:
unless #note_params.content.empty?
To this:
unless #note_params[:content].empty?
ActionController's params returns a hash of parameters.
I have a Study model which have many fields, but I'm having troubles with 1
profesion_name
so in my study model I have this
class Study < ActiveRecord::Base
attr_accessible :profesion_related, :profesion_name
attr_accessor :profesion_related
def profesion_related=(id)
if id.present?
if self.study_type_id == 4
if self.country_id == 170
#some code here
else
profesion_parent = Profesion.find(id)
new_profesion = Profesion.create({g_code: profesion_parent.g_code, mg_code: profesion_parent.mg_code, name: self.profesion_name})
self.profesion = new_profesion
end
end
end
end
end
but I'm getting an error on the line that create a Profesion, because self.profesion_name is nil
if in my controller I do this
def create
#study = Study.new(params[:study])
respond_to do |format|
#here
puts #study.to_yaml
if #study.save
.....
end
I will see in the console that profesion_name has a value
but if I do this
class Study < ActiveRecord::Base
...
def profesion_related=(id)
puts self.to_yaml
....
end
end
I can see that self.profesion_name is empty
Why could this be happening?
I want to be able to initialize a new Car object and pass it a Person object in the parameters, so it can be saved in that Person's #cars array. Currently, I take this approach:
person = Person.new("Michael")
car = Car.new("Honda", "Accord")
person.add_car(car)
person.add_car(Car.new("Ford", "Taurus"))
person.add_car(Car.new("Toyota", "Prius"))
person.display
However, I'd like to be able to create a new car instance and pass it the Person object I want it associated with. For example:
person = Person.new("Michael")
Car.new("Honda", "Accord", person)
Car.new("Toyota", "Camry", person)
Car.new("Chevy", "Tahoe", person)
person.display
Is that even possible?
class Person
attr_accessor :name
def initialize(name)
super
#name = name
#cars = []
end
def display
puts "#{#name} has #{#cars.length} cars"
puts "----------------------------"
#cars.each do |car|
puts "#{car.make} #{car.model}"
end
end
def add_car(car)
#cars.push(car)
end
end
class Car
attr_accessor :make, :model
def initialize(make, model)
#model = model
#make = make
end
def display
puts "#{#make} #{#model}"
end
end
Yes, that is possible, Car#initialize can call methods on its arguments:
class Car
def initialize(make, model, person = nil)
#model = model
#make = make
person.add_car(self) if(person)
end
#...
end
This would be my implementation:
class Car
attr_accessor :make, :model
def initialize(make, model)
self.make = make
self.model= model
end
end
Person class
class Person
attr_accessor :name, :cars
def initialize(name, cars=[])
self.name = name
self.cars = cars || []
end
def add_car(*args)
raise ArgumentError, 'invalid arguments' if (
(args.size > 2 or args.size == 0) or
(args.size == 1 and !args[0].is_a?(Car))
)
new_car = (args.size == 2) ? Car.new(*args) : args[0]
self.cars << new_car
new_car
end
end
Now you can:
person = Person.new("Michael")
car = Car.new("Honda", "Accord")
person.add_car(car)
person.add_car("Ford", "Taurus")
person.add_car("Toyota", "Prius")
person.display
The add_car method creates a new car when make and model are passed as parameters.
Yes, it is possible as mu is too short's answer demonstrated, but that doesn't really make sense in my opinion. Your cars can't be used in any context without a Person, and said parameter contributes no data necessary to construct the Car object.
I would design such an API as follows:
class Person
def initialize(name)
#name = name
end
def cars
#cars ||= Array.new # Equivalent to #cars || #cars = []
end
end
person = Person.new 'Michael'
taurus = Car.new 'Ford', 'Taurus'
prius = Car.new 'Toyota', 'Prius'
person.cars << taurus << prius << Car.new('Honda', 'Accord')
This is a simpler and more direct form of KandadaBoggu's implementation that takes advantage of the Array#<< method in order to naturally associate Cars with a Person, and also doubles as an attribute reader.