Circular require issue with Ruby - ruby

I am trying to implement a currency based application using Ruby, I find that:
require 'dollar'
require 'franc'
puts 'Why does this not work?'
class Money
attr_reader :amount
def self.dollar(number)
Dollar.new number
end
def ==(other)
self.amount == other.amount
end
def self.franc(number)
Franc.new(number)
end
end
I have the Franc class that looks like this:
require 'money'
class Franc < Money
attr_reader :amount
def initialize(amount)
#amount = number
end
def times(mul)
amount = #amount * mul
Franc.new(amount)
end
def ==(other)
return false unless other.is_a? self.class
super
end
end
This is a direct translation of some of the code from the Kent Beck book to Ruby. When I run bin/rspec I see:
/home/vamsi/Do/wycash/lib/franc.rb:3:in `<top (required)>': uninitialized constant Money (NameError)
from /home/vamsi/Do/wycash/lib/money.rb:2:in `require'
from /home/vamsi/Do/wycash/lib/money.rb:2:in `<top (required)>'
from /home/vamsi/Do/wycash/lib/dollar.rb:1:in `require'
from /home/vamsi/Do/wycash/lib/dollar.rb:1:in `<top (required)>'
from /home/vamsi/Do/wycash/spec/dollar_spec.rb:1:in `require'
from /home/vamsi/Do/wycash/spec/dollar_spec.rb:1:in `<top (required)>'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1435:in `load'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1435:in `block in load_spec_files'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1433:in `each'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/configuration.rb:1433:in `load_spec_files'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:100:in `setup'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:86:in `run'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:71:in `run'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/lib/rspec/core/runner.rb:45:in `invoke'
from /home/vamsi/Do/wycash/.bundle/ruby/2.3.0/gems/rspec-core-3.5.4/exe/rspec:4:in `<top (required)>'
from bin/rspec:17:in `load'
from bin/rspec:17:in `<main>'

I was going to add this as a comment when I realised it would start getting too lengthy.
When you say you have translated directly from Kent Beck's book I assume you are referring to his TDD by Example book (it's the only book I can find of his that references the currency example). I can't actually find an example in that book however that refers to cyclic dependancy, however from previous experience with Java and C++ you normally try to break up a cyclic dependancy by implementing an interface - for Ruby, you might want to refer to the following SO question which has good answers to this problem:
Circular Dependancies in Ruby
Having said that I think your design is broken. Your Money class should be defining the generic behaviour and attributes for all Money types. It should know nothing about Franc's or Dollars, etc... Specific behaviour for Franc should be encapsulated entirely within the Franc class. Either use a single class to handle all the currencies or use inheritance - it doesn't make much sense to do both.

You should put require 'franc' after you defined Money in your first script.
The class Money will be then defined when Ruby executes your second_script.
EDIT:
Circular require isn't an issue :
# a.rb
puts "A"
require_relative 'b'
# b.rb
puts "B"
require_relative 'a'
# ruby a.rb
A
B
A
Replacing require_relative with load './b.rb' would result in an infinite loop and "stack level too deep" Error.
Still, you should probably define Money in money.rb, Franc in franc.rb, ... and not put anything about Franc in Money.

Related

Celluloid 0.17.3 giving unexpected "undefined method" error

I have started using Celluloid gem this morning for that first time. I am following this Railscasts tutorial and trying to figure things out.
I have a class called "SomeClass" and it has only one method. Here is the code:
require 'celluloid'
class SomeClass
include Celluloid
def initialize(name)
#name = name
end
def assholify()
puts "#{#name} has become an ASSHOLE."
end
end
When I create new instances of the class and call its method (with a bang i.e. "assholify!"), I am getting the undefined method 'assholify!', error. But Celluloid is supposed to trigger the method asynchronously when it is called with a bang. So here is how I am calling the method:
names = ['John', 'Tom', 'Harry']
names.each do |name|
n = SomeClass.new name
n.assholify!
end
Here is the full backtrace of the error:
I, [2016-09-09T11:28:02.488618 #3682] INFO -- : Celluloid 0.17.3 is running in BACKPORTED mode. [ http://git.io/vJf3J ]
/home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/calls.rb:42:in `rescue in check': undefined method `assholify!' for #<SomeClass:0x10897dc> (NoMethodError)
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/calls.rb:39:in `check'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/calls.rb:26:in `dispatch'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/call/sync.rb:16:in `dispatch'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/cell.rb:50:in `block in dispatch'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/cell.rb:76:in `block in task'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/actor.rb:339:in `block in task'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/task.rb:44:in `block in initialize'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/task/fibered.rb:14:in `block in create'
from (celluloid):0:in `remote procedure call'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/call/sync.rb:45:in `value'
from /home/railsdev/.rvm/gems/ruby-2.3.1/gems/celluloid-0.17.3/lib/celluloid/proxy/sync.rb:22:in `method_missing'
from some_class.rb:18:in `block in <main>'
from some_class.rb:16:in `each'
from some_class.rb:16:in `<main>'
Why am I getting this error? Is it the right way to call the function? Also how do I get rid of Celluloid 0.17.3 is running in BACKPORTED mode. warning?
The undefined method error occurred because actor methods are not called with a bang in the recent versions of celluloid gem. Instead you call the method like this: n.async.assholify. So here is what the code should look like:
names = ['John', 'Tom', 'Harry']
names.each do |name|
n = SomeClass.new name
n.async.assholify # Instead of "n.assholify!"
end
For "Celluloid 0.17.0 is running in BACKPORTED mode" warning, take a look at this wiki. Backported Mode is the default, for a limited time. If you use require 'celluloid/current' instead of require 'celluloid', you should not see this warning.

TypeError: wrong argument type class (expected module) in rspec

i'm developing a simple net application (packed as a gem) to learn Ruby and TDD.
I have this class (receiver.rb):
require 'eventmachine'
class Receiver < EM::Connection
def initialize(port)
EM.run do
EM.open_datagram_socket('0.0.0.0', port, self)
end
end
end
And this test (receiver_spec.rb):
require "spec_helper"
require "net/receiver"
describe "Receiver" do
it "can istantiate" do
#rec = Receiver.new(500)
end
end
Anyway, when i run rspec it prints out this:
1) Receiver can istantiate
Failure/Error: #rec = Receiver.new(500)
TypeError:
wrong argument type Receiver (expected Module)
# /var/lib/gems/1.9.1/gems/eventmachine- 1.0.7/lib/eventmachine.rb:1535:in `include'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:1535:in `block in klass_from_handler'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:1535:in `initialize'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:1535:in `new'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:1535:in `klass_from_handler'
# /var/lib/gems/1.9.1/gems/eventmachine- 1.0.7/lib/eventmachine.rb:867:in `open_datagram_socket'
# ./lib/rppc/net/receiver.rb:9:in `block in initialize'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:187:in `call'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:187:in `run_machine'
# /var/lib/gems/1.9.1/gems/eventmachine-1.0.7/lib/eventmachine.rb:187:in `run'
# ./lib/rppc/net/receiver.rb:8:in `initialize'
# ./spec/net/receiver_spec.rb:6:in `new'
# ./spec/net/receiver_spec.rb:6:in `block (2 levels) in <top (required)>'
I'm quite new to the ruby environment, so if i missed something let me know.
I'm not sure what documentation you're working from, but it appears the open_datagram_socket requires a Module and cannot accept a Class as the third (handler) argument.
Per the comment in http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine.open_datagram_socket, it appears this restriction may have been loosened in a later version of EventMachine

How to define Ruby Test::Unit testcase with `must`

I had a test case of the following form:
require 'test/unit'
class SomeTests < Test::Unit::TestCase
def test_should_do_action
assert true
end
end
and have re-written it using must as suggested in the book A Test::Unit Trick to Know About:
require 'test/unit'
class SomeTests < Test::Unit::TestCase
must "do action" do
assert true
end
end
And when I run it, I get an undefined method 'must' error shown as follows:
SomeTests.rb:3:in `<class:SomeTests>': undefined method `must' for SomeTests:Class (NoMethodError) from
SomeTests.rb:2:in `<top (required)>' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb:10:in `block (2 levels) in <main>' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb:9:in `each' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb:9:in `block in <main>' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb:4:in `select' from
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb:4:in `<main>' rake aborted! Command failed with status (1): [ruby -w -I"lib" -I"/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0" "/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rake/rake_test_loader.rb" "test/**/*Tests.rb" ]
Tasks: TOP => default => test (See full trace by running task with --trace)
I thought that must might be a part of minitest, so I required 'minitest/unit' instead, but I still get an error. I also assume that must keyword isn't part of rspec, which I'm not using yet.
How do I get this to work properly?
It looks like that method is not provided out of the box, but was developed by a third party. You need to add code described here.

Why is Ruby seeing `A.run` in class B as a constant and not a class?

so this is my first question on stack overflow and I am new to Ruby, so if this is a simple question, please be nice.
I am starting off in OOP and making a game. What I think is wrong is that Ruby is thinking that a different class is a constant in the current class.
Here is my code:
./a.rb
require "./b"
class A
class << self
def run
puts "A ran."
end
end
end
./b.rb
class B
require './a'
def test
A.run
end
end
b = B.new
b.test
When I run ruby b.rb, I get:
/Users/alexstriff/Dropbox/Code/ruby/ex45/b.rb:5:in `test': uninitialized constant B::A (NameError)
from /Users/alexstriff/Dropbox/Code/ruby/ex45/b.rb:11:in `<top (required)>'
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
from /Users/alexstriff/Dropbox/Code/ruby/ex45/a.rb:1:in `<top (required)>'
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45:in `require'
from b.rb:2:in `<class:B>'
from b.rb:1:in `<main>'
So why is A.run seen as a constant by class B, instead of as a class?
Edit:
Could it possibly be a problem with the 'circular require'? I ran this under ruby -w and that came up.
Yes, as you mentioned in your edit, I believe your issue is with a circular require. As you can see, when you run ruby b.rb, you require a.rb, which then requires b.rb again, etc. Instead, I would advise removing the require in a.rb, as it does not seem to depend on b.rb.
As a separate issue, I think the error you are getting is because you don't specify that A is a top-level class. Also, having the require inside of class B does not make A accessible inside B, so I would move it outside instead:
require './a'
class B
def test
::A.run
end
end
b = B.new
b.test

has n of Strings in DataMapper

I'm writing a client for a web service, and part of their model is a list of strings belonging to a user containing previous usernames the user went by. I'm trying to use DataMapper for my client gem, and my first thought was to use DataMapper's has n syntax, but I can't seem to apply this to Strings. Is there a better way of doing this perhaps?
My current code:
class User
include DataMapper::Resource
# Some Properties here
has n, :also_known_as, 'String'
end
The error this generates is this:
irb(main):001:0> require 'cloudsdale'
NoMethodError: undefined method `relationships' for String:Class
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/associations/one_to_many.rb:109:in `finalize'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/model.rb:782:in `block in finalize_relationships'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/subject_set.rb:210:in `block in each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `block in each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/subject_set.rb:210:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/model.rb:782:in `finalize_relationships'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/model.rb:137:in `finalize'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core.rb:281:in `block in finalize'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/descendant_set.rb:64:in `block in each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/subject_set.rb:210:in `block in each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `block in each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/ordered_set.rb:319:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/subject_set.rb:210:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core/support/descendant_set.rb:63:in `each'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/dm-core-1.2.0/lib/dm-core.rb:281:in `finalize'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/cloudsdale-0.0.1/lib/cloudsdale.rb:19:in `<top (required)>'
from C:/Ruby193/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:60:in `require'
from C:/Ruby193/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:60:in `rescue in require'
from C:/Ruby193/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:35:in `require'
from (irb):1
from C:/Ruby193/bin/irb:12:in `<main>'irb(main):002:0>
The file that the error spawns from is this:
# Load in DataMapper
# Change the setup to meet your DB needs
require 'data_mapper'
DataMapper.setup(:default, 'abstract::')
# API objects
require 'cloudsdale/version'
require 'cloudsdale/api/api'
require 'cloudsdale/client'
# Models
require 'cloudsdale/models/user'
require 'cloudsdale/models/cloud'
require 'cloudsdale/models/avatar'
require 'cloudsdale/models/ban'
require 'cloudsdale/models/chat'
# Finalize DataMapper so the models Load
DataMapper.finalize
why do you use 'String' here?
has n, :also_known_as, 'String'
it make no sense, remove it:
has n, :also_known_as
if you want to set model, use :model option:
has n, :also_known_as, :model => ModelName
and i'm not sure you want to use String as model name.
most likely you need an extra model to keep user's previous names:
class UserAlias
include DataMapper::Resource
# ...
end
class User
include DataMapper::Resource
# ...
has n, :also_known_as, :model => UserAlias
end
If you want to be able to search for older usernames via the datamapper 1.x query DSL you need to define an additional model.
class User
include DataMapper::Resource
# ...
has n, :also_known_as, :model => UserNameHistory
end
class UsernameHistory
include DataMapper::Resource
property :id, Serial
property :name
belongs_to :user
end
If you do not need to query via the old usernames you can use a serialized large object. As a suggestion you could use the DataMapper::Property::YAML from dm-types like this:
class User
include DataMapper::Resource
# ...
property :also_known_as, YAML
end

Resources