Note: I am a Ruby and programming novice.
I have a class called JourneyLog I am trying to get a method called start to instantiate a new instance of another class, called Journey
class JourneyLog
attr_reader :journey_class
def initialize(journey_class: Journey)
#journey_class = journey_class
#journeys = []
end
def start(station)
journey_class.new(entry_station: station)
end
end
When I go into irbi get the following issue
2.2.3 :001 > require './lib/journeylog'
=> true
2.2.3 :002 > journeylog = JourneyLog.new
NameError: uninitialized constant JourneyLog::Journey
from /Users/BartJudge/Desktop/Makers_2018/oystercard-challenge/lib/journeylog.rb:4:in `initialize'
from (irb):2:in `new'
from (irb):2
from /Users/BartJudge/.rvm/rubies/ruby-2.2.3/bin/irb:15:in `<main>'
2.2.3 :003 >
I also have the following Rspec test
require 'journeylog'
describe JourneyLog do
let(:journey) { double :journey, entry_station: nil, complete?: false, fare: 1}
let(:station) { double :station }
let(:journey_class) { double :journey_class, new: journey }
describe '#start' do
it 'starts a journey' do
expect(journey_class).to receive(:new).with(entry_station: station)
subject.start(station)
end
end
end
I get the following Rspec failure;
1) JourneyLog#start starts a journey
Failure/Error: expect(journey_class).to receive(:new).with(entry_station: station)
(Double :journey_class).new({:entry_station=>#<Double :station>})
expected: 1 time with arguments: ({:entry_station=>#<Double :station>})
received: 0 times
# ./spec/jorneylog_spec.rb:9:in `block (3 levels) in <top (required)>'
I am at a total loss on what the problem is, or where to look for some answers.
I'm assuming I'm not injecting the Journey class properly, but thats as far as I can get myself.
Could someone provide some assistance?
In the journeylog.rb file you need to load the Journey class:
require 'journey' # I guess the Journey class is defined in lib/journey.rb
In the spec file you need to pass journey_class to the JourneyLog constructor:
describe JourneyLog do
subject { described_class.new(journey_class: journey_class) }
# ...
So, for whatever reason there is no peek method in the ruby core Queue class. I am trying to create a child class that implements the peek method. However, I don't understand why I am getting an error. Is it not possible to use instance variables in this way? Looking at the source code for Queue, there are instance variables in the constructor of the parent class. Is there a way to reference these in the subclass?
class PeekQueue < Queue
def peek
#mutex.synchronize{
while true
if #que.empty?
raise ThreadError, "queue empty" if non_block
#waiting.push Thread.current
#mutex.sleep
else
return #que[0]
end
end
}
end
end
a = PeekQueue.new
a.push(1)
a.peek
NoMethodError: undefined method 'synchronize' for nil:NilClass
Edit: The Queue class is created at compile time, which is why I couldn't find the source on the ruby source code on github. This is what the parent class looks like:
https://gist.github.com/anonymous/574e20fea3a28663bfe2
I do not see that error:
irb(main):025:0> qq = PeekQueue.new
=> #<PeekQueue:0x000006002bf498 #que=[], #num_waiting=0, #mutex=#<Mutex:0x000006002bf420>, #cond=#<ConditionVariable:0x000006002bf3f8 #waiters={}, #waiters_mutex=#<Mutex:0x000006002bf3a8>>>
irb(main):026:0> qq.peek
NameError: undefined local variable or method `non_block' for #<PeekQueue:0x000006002bf498>
from (irb):15:in `block in peek'
from (irb):12:in `synchronize'
from (irb):12:in `peek'
from (irb):26
from /usr/bin/irb:12:in `<main>'
irb(main):027:0> qq.push 1
=> #<ConditionVariable:0x000006002bf3f8 #waiters={}, #waiters_mutex=#<Mutex:0x000006002bf3a8>>
irb(main):028:0> qq.peek
=> 1
Method #non_block seems to be an issue. But access to #mutex works with your code.
I'm trying to learn Ruby, but have a previous Java background, and I thought the best way to learn Ruby is to re-implement an old side project.
The problem that I am facing is that I get the following error
ATMSystem.rb:4:in `show_start_menu': uninitialized constant ATMSystem::BankComputer (NameError)
I am using two classes, the first is the BankComputer class, and the second is ATMSystem.
class BankComputer
attr_accessor :bank_id, :customer_accounts
##card_number = 1000
def initialize(bank_id)
#bank_id = bank_id
end
def self.card_number
##card_number
end
def create_card_number
##card_number += 1
end
bc = BankComputer.new(100)
puts bc.bank_id
puts BankComputer.card_number
end
The second class:
include BankComputer.rb
class ATMSystem
def show_start_menu
bank_computer_1 = BankComputer.new(1)
end
system = ATMSystem.new()
system.show_start_menu
end
Both classes are in the same directory.
Why doesn't "include BankComputer.rb" work?
How do I import this class correctly?
I found the answer, all you have to do is
require './ClassName'
Information on what's going on here in ruby: http://coderrr.wordpress.com/2008/03/11/constant-name-resolution-in-ruby/
Doesn't help me solve my problem.. but it at least explains they 'why'
I've written the following method:
# delegate to a user permission serializer specific to the given object
# - if a serializer is not found for the given object, check the superclass
#
# #raise [NameError] if none of object, or it's superclasses have a known
# user permission serializer
# #return [UserPermission::*Serializer] returns serialized object
def self.serialized_for(object, user, klass: nil, recursion_limit: 5)
object_class = klass ? klass : object.class
# use demodulize to chop off the namespace and get the generic object name
object_name = object_class.name.demodulize
# bulid serializer name
name = "::UserPermission::#{object_name}Serializer"
begin
binding.pry
permissions = object.user_permissions(user)
return name.constantize.new(permissions)
rescue NameError => e
raise e if recursion_limit < 1
# try with super class
UserPermission.serialized_for(
object,
user,
klass: object_class.superclass,
recursion_limit: recursion_limit - 1
)
end
end
The goal is to be able to retrieve the serializer of any subclass, provided the subclass has a superclass with a serializer already defined. (I'm using ActiveModelSerializers, but that's not important here).
My problem is that I'm receiving a non-namespaced class when name.constantize runs.
My existing classes:
UserPermission
UserPermission::ProposalSerializer
PresentationSerializer < ActiveModel::Serializer
Presentation < Proposal
Proposal < ActiveRecord::Base
What I'm expecting to happen, is that when I call UserPermission.serialized_for with a Presentation, that name.constantize tries to give me a ::UserPermission::PresentationSerializer and then throw a NameError because the class doesn't exist.
What I'm getting instead is ::PresentationSerializer, which is no good - used for a different purpose.
Here is what I came up with for replicating the issue in irb:
(maybe the above context is an overly complicated explanation of this):
class NameSpace; end
class NameSpace::Klass; end
class Klass; end
class SubKlass < Klass; end
Object.const_get "::NameSpace::SubKlass"
=> SubKlass
Object.const_get("::NameSpace").const_get("SubKlass")
=> SubKlass
eval("NameSpace::SubKlass")
(eval):1: warning: toplevel constant SubKlass referenced by NameSpace::SubKlass
=> SubKlass
Is there a way I can constantize "::NameSpace::SubKlass" such that I get a NameError due to NameSpace::SubKlass not existing?
P.S.: I hope the context helps.
Edit: found another problem:
UserPermission::Template < UserPermission::Proposal
UserPermission::Template.superclass
=> Proposal
should be UserPermission::Proposal
UserPermission::Proposal
(pry):9: warning: toplevel constant Proposal referenced by UserPermission::Proposal
=> Proposal
UserPermission::Proposal is a class. So... this is a big problem. o.o
I'm using Ruby 2.1.0
Do not define your classes and modules the short-hand way. You run into scoping issues.
module UserPermission
class Proposal
end
end
module UserPermission
class Template < Proposal
end
end
UserPermission::Template.superclass
# => UserPermission::Proposal
What I am missing? I am trying to use a rest service for with Active resource, I have the following:
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
end
user = User.new(
:name => "Test",
:email => "test.user#domain.com")
p user
if user.save
puts "success: #{user.uuid}"
else
puts "error: #{user.errors.full_messages.to_sentence}"
end
And the following output for the user:
#<User:0x1011a2d20 #prefix_options={}, #attributes={"name"=>"Test", "email"=>"test.user#domain.com"}>
and this error:
/Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1322:in `load_attributes_from_response'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1316:in `create_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `tap'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `create_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `create'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1117:in `save_without_validation'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/validations.rb:87:in `save_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `save'
from import_rest.rb:22
If I user curl for my rest service it would be like:
curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"test curl", "email":"test#gmail.com"}' http://localhost:3000/users
with the response:
{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}
There is a built-in type named Data, whose purpose is rather mysterious. You appear to be bumping into it:
$ ruby -e 'Data.new'
-e:1:in `new': allocator undefined for Data (TypeError)
from -e:1
The question is, how did it get there? The last stack frame puts us here. So, it appears Data wandered out of a call to find_or_create_resource_for. The code branch here looks likely:
$ irb
>> class C
>> end
=> nil
>> C.const_get('Data')
=> Data
This leads me to suspect you have an attribute or similar floating around named :data or "data", even though you don't mention one above. Do you? Particularly, it seems we have a JSON response with a sub-hash whose key is "data".
Here's a script that can trigger the error for crafted input, but not from the response you posted:
$ cat ./activeresource-oddity.rb
#!/usr/bin/env ruby
require 'rubygems'
gem 'activeresource', '3.0.10'
require 'active_resource'
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
end
USER = User.new :name => "Test", :email => "test.user#domain.com"
def simulate_load_attributes_from_response(response_body)
puts "Loading #{response_body}.."
USER.load User.format.decode(response_body)
end
OK = '{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}'
BORKED = '{"data":{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}'
simulate_load_attributes_from_response OK
simulate_load_attributes_from_response BORKED
produces..
$ ./activeresource-oddity.rb
Loading {"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}..
Loading {"data":{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}..
/opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
from ./activeresource-oddity.rb:17:in `simulate_load_attributes_from_response'
from ./activeresource-oddity.rb:24
If I were you, I would open /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb, find load_attributes_from_response on line 1320 and temporarily change
load(self.class.format.decode(response.body))
to
load(self.class.format.decode(response.body).tap { |decoded| puts "Decoded: #{decoded.inspect}" })
..and reproduce the error again to see what is really coming out of your json decoder.
I just ran into the same error in the latest version of ActiveResource, and I found a solution that does not require monkey-patching the lib: create a Data class in the same namespace as the ActiveResource object. E.g.:
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
class Data < ActiveResource::Base; end
end
Fundamentally, the problem has to do with the way ActiveResource chooses the classes for the objects it instantiates from your API response. It will make an instance of something for every hash in your response. For example, it'll want to create User, Data and Pet objects for the following JSON:
{
"name": "Bob",
"email": "bob#example.com",
"data": {"favorite_color": "purple"},
"pets": [{"name": "Puffball", "type": "cat"}]
}
The class lookup mechanism can be found here. Basically, it checks the resource (User) and its ancestors for a constant matching the name of the sub-resource it wants to instantiate (i.e. Data here). The exception is caused by the fact that this lookup finds the top-level Data constant from the Stdlib; you can therefore avoid it by providing a more specific constant in the resource's namespace (User::Data). Making this class inherit from ActiveResource::Base replicates the behaviour you'd get if the constant was not found at all (see here).
Thanks to phs for his analysis - it got me pointed in the right direction.
I had no choice but to hack into ActiveResource to fix this problem because an external service over which I have no control had published an API where all attributes of the response were tucked away inside a top-level :data attribute.
Here's the hack I ended up putting in config/initializers/active_resource.rb to get this working for me using active resource 3.2.8:
class ActiveResource::Base
def load(attributes, remove_root = false)
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
#prefix_options, attributes = split_options(attributes)
if attributes.keys.size == 1
remove_root = self.class.element_name == attributes.keys.first.to_s
end
# THIS IS THE PATCH
attributes = ActiveResource::Formats.remove_root(attributes) if remove_root
if data = attributes.delete(:data)
attributes.merge!(data)
end
# END PATCH
attributes.each do |key, value|
#attributes[key.to_s] =
case value
when Array
resource = nil
value.map do |attrs|
if attrs.is_a?(Hash)
resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs)
else
attrs.duplicable? ? attrs.dup : attrs
end
end
when Hash
resource = find_or_create_resource_for(key)
resource.new(value)
else
value.duplicable? ? value.dup : value
end
end
self
end
class << self
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
# THIS IS THE PATCH
body = (format.decode(connection.get(path, headers).body) || [])
body = body['data'] if body['data']
instantiate_collection( body, prefix_options )
# END PATCH
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
end
end
I solved this using a monkey-patch approach, that changes "data" to "xdata" before running find_or_create_resource_for (the offending method). This way when the find_or_create_resource_for method runs it won't search for the Data class (which would crash). It searches for the Xdata class instead, which hopefully doesn't exist, and will be created dynamically by the method. This will be a a proper class subclassed from ActiveResource.
Just add a file containig this inside config/initializers
module ActiveResource
class Base
alias_method :_find_or_create_resource_for, :find_or_create_resource_for
def find_or_create_resource_for(name)
name = "xdata" if name.to_s.downcase == "data"
_find_or_create_resource_for(name)
end
end
end