class Book
def isBook()
return true
end
def initialize(isbn, userID)
#isbn = isbn
#userID = userID
end
def ==(var)
if(var.isbn == #isbn && var.userID == #userID)
return true
end
return false
end
def print()
"ISBN: %{#isbn}\nWypozyczono przez: %{#userID}"
end
end
class BookList
def initialize()
#arr = Array.new()
end
def add(book)
if(book.isBook())
#arr.push(book)
end
end
def at(var)
#arr[var].print()
end
end
booklist = BookList.new()
print booklist
booklist.add(Book.new(1231231231231, "d_zeglen"))
print BookList.at(0)
I don't know where did I made mistake. When I try to run this program, ruby prints into console:
undefined method 'at' for BookList:Class (No method error)
Anybody got idea what's wrong here?
#at is an instance method of the instances of the class BookList, not a class method. Thus below will work
print booklist.at(0)
Here is the code :-
class Book
def isBook()
return true
end
def initialize(isbn, userID)
#isbn = isbn
#userID = userID
end
def ==(var)
if(var.isbn == #isbn && var.userID == #userID)
return true
end
return false
end
def print()
"ISBN: %{#isbn}\nWypozyczono przez: %{#userID}"
end
end
class BookList
def initialize()
#arr = Array.new()
end
def add(book)
if(book.isBook())
#arr.push(book)
end
end
def at(var)
#arr[var].print()
end
end
booklist = BookList.new()
print booklist
booklist.add(Book.new(1231231231231, "d_zeglen"))
print booklist.at(0)
# >> #<BookList:0xa013de4>ISBN: %{#isbn}
# >> Wypozyczono przez: %{#userID}
Related
I am trying to write some functionality which allows me to take a broken bike to a garage to have it fixed, whilst implementing this i have come across this issue. i understand that this usually is because it cannot find the file but have ran out of ideas on how to fix it. If you need any of the other code let me know but i thought this would be all the code needed.
dockingstation.rb:
require_relative 'Bike'
require_relative 'van'
require_relative 'garage'
class DockingStation
attr_reader :dock, :max_dock, :DEFAULT_CAPACITY
DEFAULT_CAPACITY = 20
def initialize(capacity = DEFAULT_CAPACITY)
#max_dock = capacity
#dock = []
end
def release_bike
if !empty?
#dock.each_with_index do |bike, index|
if bike.working?
#dock.delete_at(index)
return bike
else
raise "Bike is broken"
end
end
else
raise "No bikes to release"
end
end
def dock_bike(bike, working = true)
if !full?
#dock << bike
else
raise "Too many bikes"
end
end
def show_dock
#dock.each do |el|
return el
end
end
def van_takes_broken_bikes(van)
puts #dock
#dock.each_with_index do |bike, index|
if bike.working? == true
van.storage.append(bike)
else
van.storage.append(bike)
end
end
end
private def full?
#dock.length == #max_dock
end
private def empty?
#dock.length == 0
end
end
dock = DockingStation.new
bike = Bike.new
van = Van.new
bike.report_broken
dock.dock_bike(bike)
dock.van_takes_broken_bikes(van)
van.rb:
require_relative 'dockingstation'
require_relative 'garage'
class Van
attr_reader :storage
def initialize
#storage = []
end
def take_broken_bikes_to_garage(bikes)
end
end
Let see the code first that will help what I want to achieve:
class PostalInfo
attr_reader :name, :code
def initialize (id, name, code)
#id = id
#name = name
#code = code
end
def method_missing(method, *args, &blk)
if method.to_s == "#{name}"
return code
else
super
end
end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
So when I run below code, it gives output as:
pi1.united_states => 'US'
pi2.united_kingdom => 'UK'
its fine upto here, but I also want to do something like
PostalInfo.united_states => 'US'
PostalInfo.united_kingdom => 'UK'
how to do that, thanks in advance
This sets up a class attribute to hold the data, and whenever an instance is initialized, it adds to that data structure, and uses a similar class-level method_missing.
class PostalInfo
attr_reader :name, :code
##postal_info = {}
def self.method_missing(method, *args, &blk)
name = method.to_s
if ##postal_info[name]
##postal_info[name]
else
super
end
end
def initialize (id, name, code)
#id = id
#name = name
#code = code
##postal_info[#name] = #code
end
def method_missing(method, *args, &blk)
if method.to_s == "#{name}"
return code
else
super
end
end
end
pi1 = PostalInfo.new(1, 'united_states', 'US')
pi2 = PostalInfo.new(2, 'united_kingdom', 'UK')
PostalInfo.united_states #=> 'US'
PostalInfo.united_kingdom #=> 'UK'
I will say, this seems like a weird design, and I'd normally recommend avoiding using mutable state with class methods, and method_missing wherever possible.
You can write something like this:
class PostalInfo
POSTAL_HASH = {
united_states: 'US',
united_kingdom: 'UK',
}.freeze
def self.method_missing(method, *args, &blk)
POSTAL_HASH[method] || super
end
end
Skipping missing method might result in better performance:
class PostalInfo
POSTAL_HASH = {
united_states: 'US',
united_kingdom: 'UK',
}.freeze
class << self
POSTAL_HASH.each do |name, code|
define_method(name) do
code
end
end
end
end
With one exception, you need to mimic the code in the first part of your answer in the class' singleton class. The difference concerns the initialisation of the instance variables. Rather than using PostalInfo::new and PostalInfo#initialize, you need to create a class method for doing that (which I've called add_country_data). Note that as the class' instance variable id is not used I've not included it in the code.
class PostalInfo
class << self
attr_reader :country_data
def add_country_data(name, code)
(#country_data ||= {})[name] = code
end
def add_country_data(name, code)
#country_data[name] = code
end
def method_missing(method, *args, &blk)
return country_data[method.to_s] if country_data.key?(method.to_s)
super
end
end
end
PostalInfo.add_country_data('united_states', 'US')
PostalInfo.add_country_data('united_kingdom', 'UK')
PostalInfo.united_states
#=> "US"
PostalInfo.united_kingdom
#=> "UK"
PostalInfo.france
#=> NoMethodError (undefined method `france' for PostalInfo:Class)
Though this meets your requirement, I would be inclined to construct the class in a more conventional way:
class PostalInfo
attr_reader :name, :code
#instances = []
def initialize(name, code)
#name = name
#code = code
self.class.instances << self
end
singleton_class.public_send(:attr_reader, :instances)
end
us = PostalInfo.new('united_states', 'US')
uk = PostalInfo.new('united_kingdom', 'UK')
us.code
#=> "US"
uk.code
#=> "UK"
PostalInfo.instances
#=> [#<PostalInfo:0x00005c1f24c5ccf0 #name="united_states", #code="US">,
# #<PostalInfo:0x00005c1f24c71858 #name="united_kingdom", #code="UK">]
How to refactor #filtered method?
In Hanami there is no way to make a chain of queries (filters) in ActiveRecord-style. I would like to get a methods like ActiveRecord filters.
Now: documents.filtered(genre: 'news', min_published_at: from, max_published_at: to, skip: 30)
What I want: documents.with_genre('news').published_between(from, to).skip(30)
class DocumentRepository < Hanami::Repository
GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
DOCUMENTS_PER_PAGE = 30
associations do
has_many :boxes
has_many :urls
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
def filtered(params = {})
result = ordered.limit(DOCUMENTS_PER_PAGE)
result = result.where(genre: params[:genre]) if params.key?(:genre)
if params.key?(:min_created_at) && params.key?(:max_created_at)
date_range = params[:min_created_at]..params[:max_created_at]
result = result.where(created_at: date_range)
end
if params.key?(:min_published_at) && params.key?(:max_published_at)
date_range = params[:min_published_at]..params[:max_published_at]
result = result.where(published_at: date_range)
end
result = result.offset(params[:skip]) if params.key?(:skip)
result
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/AbcSize
def ordered
documents.order { created_at.desc }
end
end
Something along these lines might work, but not sure how chaining these will poorly effect performance or results, but you can try it and it may lead you to the answer you want
UPDATED
If you really want chaining this is close to what you want.
class DocumentRepository < Hanami::Repository
GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
DOCUMENTS_PER_PAGE = 30
associations do
has_many :boxes
has_many :urls
end
attr_accessor :data
def initialize
#data = []
super
end
def data
#data.flatten!.uniq!
end
def with_genre(key)
#data << documents.where(genre: key)
self
end
def published_between(arr)
from, to = arr
#data << documents.where(created_at: [from..to])
self
end
def skip(num)
#data << documents.offset(num)
self
end
end
Call it like this assuming this is an instance variable of DocumentRepository
document_repository.with_genre('news')
.published_between([from, to])
.skip(30)
.data
By returning self in each instance method you're able to chain the calls on the instance.
Original answer
This way works but uses similar syntax in your current call.
class DocumentRepository < Hanami::Repository
GENRES = DbSchema.current_schema.enum(:document_genre).values.map(&:to_s)
DOCUMENTS_PER_PAGE = 30
associations do
has_many :boxes
has_many :urls
end
def hack_where(opts={})
data = []
opts.each do |i|
data << self.send(i[0],i[1]).call
end
data.flatten!.uniq!
end
def with_genre(key)
lambda { |key| documents.where(genre: key) }
end
def published_between(arr)
from = arr[0]
to = arr[1]
lambda { |from, to| documents.where(created_at: [from..to]) }
end
def skip(num)
lambda { documents.offset(num) }
end
end
You can call it like:
hack_where({with_genre: 'news', published_between: [from,to], skip: 30})
Introduce query object:
class FilterDocuments
DOCUMENTS_PER_PAGE = 30
def initialize(documents)
#documents = documents
end
def filter(params = {})
result = apply_ordering(documents)
result = apply_limit_and_offset(result, params)
result = filter_by_genre(result, params)
result = filter_by_created_at(result, params)
result = filter_by_published_at(result, params)
result
end
private
attr_reader :documents
def apply_ordering(documents)
documents.order { created_at.desc }
end
def apply_limit_and_offset(documents, params)
if params.key?(:skip)
documents.offset(params[:skip])
else
documents
end.limit(DOCUMENTS_PER_PAGE)
end
def filter_by_genre(documents, params)
if params.key?(:genre)
documents.where(genre: params[:genre])
else
documents
end
end
def filter_by_created_at(documents, params)
if params.key?(:min_created_at) && params.key?(:max_created_at)
range = params[:min_created_at]..params[:max_created_at]
documents.where(created_at: range)
else
documents
end
end
def filter_by_published_at(documents, params)
if params.key?(:min_published_at) && params.key?(:max_published_at)
range = params[:min_published_at]..params[:max_published_at]
documents.where(published_at: range)
else
documents
end
end
end
How to use:
def query
FilterDocuments.new(DocumentRepository.new.documents)
end
filtered_documents = query.filter(params)
There is the following code:
class MyOpenStruct
def initialize(initial_values = {})
#values = initial_values
end
def _singleton_class
class << self
self
end
end
def method_missing(name, *args, &block)
if name[-1] == "="
base_name = name[0..-2].intern
puts "add_method_to_set"
self.class.add_method_to_set(base_name)
#values[base_name] = args[0]
else
puts "add_method_to_get"
self.class.add_method_to_get(base_name)
#values[name]
end
end
def self.add_method_to_get(name)
define_method(name) do |value|
#values[name]
end
end
def self.add_method_to_set(name)
define_method(name) do |value|
#values[name] = value
end
end
end
obj1 = MyOpenStruct.new(name: "Dave")
obj1.address = "1"
obj2 = MyOpenStruct.new(name: "Dave")
obj2.address = "2"
I want to do the following thing: when I execute some method (obj1.address) and it's missing I want to add this method to my MyOpenStruct class. But when I execute my code I get 'missing' two times instead of one. Why? I don't understand. Please explain it to me. Thanks.
#koffeinfrei identified one problem with you code, but I found a few others. Below I have what I believe to be a corrected version. I have also suggested an alternative way to structure the code. My main advice is to pull out the dynamic creation of instance methods, as that is quite generic. You might even put that in a module with other methods that you could include as needed.
Your code with repairs
class MyOpenStruct
def initialize(initial_values = {})
#values = initial_values
end
def method_missing(name, *args, &block)
puts "in mm, name = #{name}"
if name[-1] == "="
base_name = name[/\w+/]
puts "add_method_to_set: '#{name}'"
self.class.add_method_to_set(base_name)
#values[base_name.to_sym] = args[0]
else
puts "add_method_to_get: '#{name}'"
self.class.add_method_to_get(name)
#values[name.to_sym]
end
end
def self.add_method_to_get(name)
define_method(name.to_sym) do
#values[name.to_sym]
end
end
def self.add_method_to_set(name)
define_method((name+'=').to_sym) do |value|
#values[name.to_sym] = value
end
end
end
Alternative construction
def create_instance_eval(klass, method, &block)
klass.class_eval { define_method(method, &block) }
end
class MyOpenStruct
def initialize(initial_values = {})
#values = initial_values
end
def method_missing(name, *args, &block)
if name[-1] == "="
base_name = name[/\w+/]
method_name = (base_name+'=').to_sym
puts "create method '#{method_name}'"
method = create_instance_eval(self.class, method_name) do |value|
#values[base_name.to_sym] = value
end
send(method, args[0])
else
method_name = name.to_sym
puts "create method '#{method_name}'"
method = create_instance_eval(self.class, method_name) do
#values[method_name]
end
send(method)
end
end
end
Example
MyOpenStruct.instance_methods(false)
#=> [:method_missing]
obj1 = MyOpenStruct.new(name: "Dave")
#=> #<MyOpenStruct:0x00000102805b58 #values={:name=>"Dave"}>
obj1.address = "1"
# create method 'address='
#=> "1"
MyOpenStruct.instance_methods(false)
#=> [:method_missing, :address=]
obj2 = MyOpenStruct.new(name: "Mitzy")
#=> #<MyOpenStruct:0x00000101848878 #values={:name=>"Mitzy"}>
obj2.address = 2
#=> 2
obj2.address
# create method 'address'
# => 2
MyOpenStruct.instance_methods(false)
$#=> [:method_missing, :address=, :address]
obj1.instance_variable_get(:#values)
#=> {:name=>"Dave", :address=>"1"}
obj2.instance_variable_get(:#values)
#=> {:name=>"Mitzy", :address=>2}
The method name for the setter method needs to have the trailing =, so you need to define the method with the name instead of the base_name.
self.class.add_method_to_set(name)
I'm a new Rubyist and am wondering how I can access the ingredients class from individual cookies? As we all know, cookies are made of different ingredients. How can I specify default ingredients for individual cookies without setting default values? Even if I had default values, how would I update those to reflect the most current "recipe"? Please and thanks!
#Cookie Factory
module CookieFactory
def self.create(args)
cookie_batch = []
args.each do |cookie|
cookie_batch << PeanutButter.new if cookie == "peanut butter"
cookie_batch << ChocholateChip.new if cookie == "chocolate chip"
cookie_batch << Sugar.new if cookie == "sugar"
end
return cookie_batch
end
end
#Classes/Subclasses
class Ingredients
attr_reader
def initialize(contents = {})
# contents = defaults.merge(contents)
#sugar = contents.fetch(:sugar, "1.5 cups")
#salt = contents.fetch(:salt, "1 teaspoon")
#gluten = contents.fetch(:gluten, "0")
#cinnamon = contents.fetch(:cinnamon, "0.5 teaspoon")
end
end
class Cookie
attr_reader :status, :ingredients
def initialize(ingredients = {})
#ingredients = ingredients
#status = :doughy
super()
end
def bake!
#status = :baked
end
end
class PeanutButter < Cookie
attr_reader :peanut_count
def initialize
#peanut_count = 100
super()
end
def defaults
{
:peanut_shells => 5
}
end
end
class Sugar < Cookie
attr_reader :sugar
def initialize
#sugar = "1_cup"
super()
end
end
class ChocholateChip < Cookie
attr_reader :choc_chip_count
def initialize
#choc_chip_count = 200
super()
end
end
You can use Hash#merge to acheive this behavior:
class PeanutButter < Cookie
attr_reader :peanut_count
def initialize(ingredients)
#peanut_count = 100
super(ingredients.merge(defaults))
end
def defaults
{
:peanut_shells => 5
}
end
end