ruby, no method error - ruby

I am receiving the following error when running my below ruby script:
s3parse.rb:12:in `block in <class:AccountLog>': undefined method `extract_account_id' for AccountLog:Class (NoMethodError)
I dont think it should be a class method, is there a reason its not taking my method into account?
class AccountLog
attr_accessor :bytes, :account_id, :date
def extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
end

def extract_account_id will define an instance method.
In the way you call it, you need a class method instead.
Define it like this:
def self.extract_account_id(line)
or, as you already have an AccountLog instance, use it to call extract_account_id:
account_log.account_id = account_log.extract_account_id(line)
Please note that with second way you do not need to alter method definition, just call extract_account_id via account_log instance.
And i guess you would want to put s3log = File... outside class definition.
Or use a constant instead: S3log = ...
Then you'll can access it as AccountLog::S3log

Is there any reason you don't think it should be a class method? You are using it in the context of a class method and that's why it it's saying no such method for class AccountLog.
If you name your method as self.extract_account_id(line) I'm sure it will work.
From what you are trying to do I think this is what you are looking for?
class AccountLog
attr_accessor :bytes, :account_id, :date
def self.extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end

While you could take the class method approach, there seems to be a little more going on.
You should put the extraction logic in a method in itself rather than let it hangout in your class. Then outside of the class, have an instance of AccountLog where you can call on the methods for log and account id extraction. At that point you can do something with those values.
Class method or not is a detail we can explore after the class is a bit more clean I think.

Related

How to stub class instantiated inside tested class in rspec

I have problem stubbing external api, following is the example
require 'rspec'
require 'google/apis/storage_v1'
module Google
class Storage
def upload file
puts '#' * 90
puts "File #{file} is uploaded to google cloud"
end
end
end
class UploadWorker
include Sidekiq::Worker
def perform
Google::Storage.new.upload 'test.txt'
end
end
RSpec.describe UploadWorker do
it 'uploads to google cloud' do
google_cloud_instance = double(Google::Storage, insert_object: nil)
expect(google_cloud_instance).to receive(:upload)
worker = UploadWorker.new
worker.perform
end
end
I'm trying to stub Google::Storage class. This class is instantiated inside the object being tested. How can I verify the message expectation on this instance?
When I run above example, I get following output, and it seems logical, my double is not used by tested object
(Double Google::Storage).upload(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
I'm new to Rspec and having hard time with this, any help will be appreciated.
Thanks!
Reaching for DI is always a good idea (https://stackoverflow.com/a/51401376/299774) but there are sometimes reasons you can't so it, so here's another way to stub it without changing the "production" code.
1. expect_any_instance_of
it 'uploads to google cloud' do
expect_any_instance_of(Google::Storage).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
In case you just want to test that the method calls the method on any such objects.
2. bit more elaborated setup
In case you want to control or set up more expectations, you can do this
it 'uploads to google cloud' do
the_double = instance_double(Google::Storage)
expect(Google::Storage).to receive(:new).and_return(the_double)
# + optional `.with` in case you wanna assert stuff passed to the constructor
expect(the_double).to receive(:insert_object)
worker = UploadWorker.new
worker.perform
end
Again - Dependency Injection is clearer, and you should aim for it. This is presented as another possibility.
I would consider reaching for dependency injection, such as:
class UploadWorker
def initialize(dependencies = {})
#storage = dependencies.fetch(:storage) { Google::Storage }
end
def perform
#storage.new.upload 'test.txt'
end
end
Then in the spec you can inject a double:
storage = double
expect(storage).to receive(...) # expection
worker = UploadWorker.new(storage: storage)
worker.perform
If using the initializer is not an option then you could use getter/setter method to inject the dependency:
def storage=(new_storage)
#storage = new_storage
end
def storage
#storage ||= Google::Storage
end
and in the specs:
storage = double
worker.storage = storage

Sinatra Multiple Parallel Requests Variable Behaviour

I am fairly new to ruby and would like to understand how class instance variables behave in case of multiple parallel requests.
I have a method inside my controller class which is called everytime for each request for a specific operation (create in this case)
class DeployProvision
def self.create(data)
raise "Input JSON not received." unless data
# $logger.info input_data.inspect
failure = false
response_result = ""
response_status = "200"
#validator = SchemaValidate.new
validation = #validator.validate_create_workflow(data.to_json)
end
end
This method is called as (DeployProvision.create(data))
I am a little confused on how #validator class instance variable behaves when multiple requests come. Is it shared among multiple requests. Is it a good idea to declare this as class instance variable instead of a local variable ?
I am working on an existing code base and would like to understand the intent of creating #validator as a class instance variable instead of local variable.
You can write ultra-simple script like this:
require 'sinatra'
class Foo
def self.bar
#test = Time.now
puts #test
end
end
get '/' do
Foo.bar
end
and you'll see it does nothing, because with every call, you're creating new instance of Time(SchemaValidate in your code).
If you used memoization and had something like #validator ||= SchemaValidate.new you would have one instance of SchemaValidate stored between requests.
I don't think that'd change anything in terms of performance and I don't have idea why would anyone do something like that.
You can have some fun with ultra-simple scripts with sinatra to test how it behaves.
Good luck with this code!

Issue loading classes order EDIT: works, although some odd behavior along the way

I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end

ruby variable scoping across classes

RuNubie here. I've got a class Login that logs into gmail using the net/IMAP library. What is happening is that I create a new instance of that class, such as:
a = Login.new("username", "gmail.com", "passw")
Then, I'm working on other classes that will do some "stuff" with the mailbox. The problem is that the #imap variable I've defined in Login seems to have disappeared (due to scoping I assume).
This is how #imap is declared in Login class:
#imap = Net::IMAP.new('imap.gmail.com',993,true,nil,false)
So this:
#today = Date.today
#received_today = imap.search(["SINCE", #today.strftime("%d-%b-%Y")]).count.to_s
...returns an error. These are the two errors I've gotten while playing around with this. The first one is when I use imap, the second one is when I try #imap:
NameError: undefined local variable or method `imap' for #<Object:0x10718d2a8>
NoMethodError: undefined method `search' for nil:NilClass
What are the best practices for dealing with a situation like this? Is the only solution to define my methods that do "stuff" in the same class where I'm creating the new instance of Net::IMAP? Is declaring #imap as a global variable $imap a bad practice? So confused, I bet the answer is very simple and obvious too, but I'm just not seeing it. Thanks!
This:
#received_today = imap.search(["SINCE", #today.strftime("%d-%b-%Y")]).count.to_s
won't work because, well, there is no imap in scope at that point and so you get a NameError. When you try it like this:
#received_today = #imap.search(["SINCE", #today.strftime("%d-%b-%Y")]).count.to_s
You get a NoMethodError because instance variables, such as #imap, are automatically created at first use and initialized as nil. Your real #imap is in another object so you can't refer to it as #imap anywhere else.
I think you want a structure more like this:
class User
def imap
if(!#imap)
#imap = Net::IMAP.new('imap.gmail.com', 993, true, nil, false)
# and presumably an #imap.authenticate too...
end
#imap
end
end
class OtherOne
def some_method(user)
#today = Date.today
#received_today = user.imap.search(["SINCE", #today.strftime("%d-%b-%Y")]).count.to_s
end
end
Keep your Net::IMAP localized inside your User and let other objects use it by providing a simple accessor method.
Oh and that global $imap idea, I'll just pretend I didn't see that as globals are almost always a really bad idea.
a shorter way to define the imap variable in the User class, which is pretty much the same as what mu posted:
class User
def imap
#imap ||= Net::IMAP.new...
end
end

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources