I've got a ruby exercise, and cannot quite get past one point.
When I run the tests, it throws undefined method "attribute" for CommentSerializer:Class.
Though, there is such a method defined in serializer.rb, from which it's being inherited.
Am I missing something about inheritance in ruby here?
Note: I am neither allowed to add any gems other than the two listed below, nor to modify any file other than serializer.rb.
Here are the files:
Gemfile:
gem 'rspec'
gem 'pry'
app/comment.rb:
Comment = Struct.new(:id, :body)
app/comment_serializer.rb:
require_relative "serializer"
class CommentSerializer < Serializer
attribute :id
attribute :body
end
app/serializer.rb:
class Serializer
def initialize(object)
#obj = object
end
def serialize
obj.members.inject({}) do |hash, member|
hash[member] = obj[member]
hash
end
end
def attribute(key)
end
private
def obj
#obj
end
end
spec/comment_serializer_spec.rb:
require "date"
require_relative "spec_helper"
require_relative "../app/comment"
require_relative "../app/comment_serializer"
RSpec.describe CommentSerializer do
subject { described_class.new(comment) }
let(:comment) do
Comment.new(1, "Foo bar")
end
it "serializes object" do
expect(subject.serialize).to eq({
id: 1,
body: "Foo bar",
})
end
end
If you call something like attribute in the body of the class definition then it happens in the class context at that exact moment, as in:
class Example < Serializer
# This is evaluated immediately, as in self.attribute(:a) or Example.attribute(:a)
attribute :a
end
There must be a corresponding class method to receive that call, as in:
class Serializer
def self.attribute(name)
# ...
end
end
Since you're inheriting that method it will be defined prior to calling it, but that's not the case if you have something like:
class Example
attribute :a # undefined method `attribute' for Example:Class (NoMethodError)
def self.attribute(name)
end
end
The method is defined after it's called, so you get this error. You must either reverse the order, define first, call second, or put it into a parent class.
Related
How can one get the class a method was defined in?
I've found how to look up descendents and ansestors: Look up all descendants of a class in Ruby
But that doesn't necessarily get me the defining class (last defining class really).
I've found how to get the calling class:
Ruby Inheritance Get Caller Class Name
But I want the opposite. I would like how to get the defining class.
I've also tried Module.nesting. That gets me what I want in this case, but I worry it will be inconsistent and not acceptable in a larger codebase of which I don't have ultimate control.
puts RUBY_VERSION
# Test class vs super.
class Super
def test_func
puts "#{self.class}, #{ __method__}"
end
end
class Child < Super
def test_func2
self.test_func
end
end
Child.new.test_func
I had hoped for:
1.8.7
Super, test_func
But got:
1.8.7
Child, test_func
You asked self.class of Child object and you got it.
You need use Method#owner to return the class or module that defines the method.
class Super
def test_func
puts "#{method(__method__).owner}, #{ __method__}"
end
end
class Child < Super
def test_func2
self.test_func
end
end
Child.new.test_func
# will print: Super, test_func
or just
Child.new.method(:test_func).owner
#=> Super
Even though I required Nokogiri and initialized the variable, my method does not have access to the Nokogiri methods. I want to do this:
class Requester
require 'nokogiri'
def initialize(body)
#body = body
end
def destination
#body.at_css('destination')
end
end
and then I pass body, which is a Nokogiri document.
mess = Requester.new(body)
When I do this I get a "No Method Error":
mess.destination
I don't understand. I thought my class would have all the Nokogiri methods if I require it.
The full error is on at_css and looks like:
NoMethodError (undefined method `at_css' for #<Requester:0x007f685d971478>
You are confusing require and include.
require loads a file or a gem.
include includes the methods of another object.
a.rb:
module A
def hello
"hello world"
end
end
b.rb:
require 'a'
class B
include A
end
puts B.new.hello # hello world
However, you really need to rethink what you are trying to do. You can't include a class - you extend classes. And the object you are looking for is the class Nokogiri::HTML::Document.
If you are trying to build a document crawler you can use the delegator pattern:
require 'nokogiri'
class Requester < Delegator
def initialize(body)
super
#body = body
#doc = Nokogiri::HTML(body)
end
def __getobj__
#doc
end
end
Or you would create a subclass of Nokogiri::HTML::Document.
http://www.nokogiri.org/tutorials/searching_a_xml_html_document.html
http://ruby-doc.org/stdlib-2.2.1/libdoc/delegate/rdoc/Delegator.html
So i'm experiencing this weird behavior while testing a ruby class. I'm using rspec 3 to test it by the way.
Class Foo has a method 'fetch_object' which calls the 'find' method from class Bar to retrieve an object and than calls the method 'fail' from the fetched object.
The so called weird behavior happens when i expect to receive the method 'fail' once and receive none but if I change the method name for 'faill' it works like a charm :S
here is the drama:
require 'ostruct'
class Foo
def fetch_object
foobar = Bar.find
foobar.fail
end
end
class Bar
def self.find
OpenStruct.new(name: 'Foo Bar')
end
end
describe Foo do
subject { Foo.new }
let(:foo) { OpenStruct.new() }
before do
expect(Bar).to receive(:find).and_return(foo)
end
it 'fetch object with name' do
expect(foo).to receive(:fail)
subject.fetch_object
end
end
I suspect it's because you are setting an expectation on object, which behaviour depends on method_missing (OpenStruct).
For that reason I wouldn't want it as a mock, I would use regular mock (and spec will pass):
let(:foo) { double('foobar') }
You are testing here, if returned object (result of Bar.find) will receive an expected message, without going into implementation details.
Setting expectations on Dynamic classes like ostruct may lead to strange results. It seems that at some point a Kernel#fail method is invoked, thus, changing a name to faill or any other that is not already "taken" by Kernel will make it work.
Other solution would be monkeypatching OpenStruct to avoid method_missing beeing called:
class OpenStruct
def fail
true
end
end
class Foo
def fetch_object
foobar = Bar.find
foobar.fail
end
end
class Bar
def self.find
OpenStruct.new(name: 'Foo Bar')
end
end
describe Foo do
subject { Foo.new }
let(:foo) { OpenStruct.new }
before do
expect(Bar).to receive(:find).and_return(foo)
end
it 'fetch object with name' do
expect(foo).to receive(:fail)
subject.fetch_object
end
end
But I don't know why would you want to do that ;)
More info: Doubles and dynamic classess
I have a rails application and a class I've wrote as a part of it (not an ActiveRecord or anything..). The data is stored in simple instance variables (string, integers, arrays...)
When I invoke to_json on an instance of it I get what I expect to. A JSON object, containing all instance variables as JSON objects too.
However, when I add include Enumerable to the class definition, the behavior of to_json changes and I get an empty object: "[]"
Any idea why is that? Does Enumerable somehow defined or undefines something that affects to_json?
Thanks!
So, what happens is:
Rails loads in ActiveSupport. ActiveSupport injects (monkey patches) these as_json methods into several classes and modules, including Enumerable:
module Enumerable
def as_json(options = nil) #:nodoc:
to_a.as_json(options)
end
end
You're probably returning nothing for the each method Enumerable requires you to have, so to_a returns [], and an empty array gets converted into the String "[]".
What you can do here, is, to bind your object into a non-enumerable inherited class, and use its .as_json method.
Like this:
class A
def as_json(*)
Object.instance_method(:as_json).bind(self).call
end
end
Demo:
➜ pry
require 'active_support/all'
=> true
class A
def initialize
#a = 1
end
end
=> nil
A.new.to_json
=> "{\"a\":1}"
class A
include Enumerable
def each
end
end
=> nil
A.new.to_json
=> "[]"
class A
def as_json(*)
Object.instance_method(:as_json).bind(self).call
end
end
=> nil
A.new.to_json
=> "{\"a\":1}"
I have a httparty "model" which I use like so
myRest = RestModel.new
myRest.someGetResquest()
myRest.somePostRequest()
How would I go about changing it to work similarly to an activemodel, like so?
RestModel.someGetRequest()
RestModel.somePostRequest()
this blog post shows how to include the singleton module but its still accesses the instance like this: RestModel.instance.someGetRequest()
here is my code:
class Managementdb
include HTTParty
base_uri "http://localhost:7001/management/"
def initialise(authToken)
self.authToken = authToken
end
def login()
response = self.class.get("/testLogin")
if response.success?
self.authToken = response["authToken"]
else
# this just raises the net/http response that was raised
raise response.response
end
end
attr_accessor :authToken
...
end
Please tell me that I am doing it all wrong (show me the light)
You want to use extend rather than include, which will add the methods onto the class singleton, rather than making them available on instances.
class Managementdb
extend HTTParty
end
a longer example illustrating this:
module Bar
def hello
"Bar!"
end
end
module Baz
def hello
"Baz!"
end
end
class Foo
include Bar
extend Baz
end
Foo.hello # => "Baz!"
Foo.new.hello # => "Bar!"