rails method chaining context - ruby

I have what is probably a basic Q, but it appears complex in the setup. I have a module that has some classes. One class contains methods for API calls. Other classes describe a server. Dev for instance has its attributes. The server classes inherit the class that contains all the API calls. I use an instance of the server class to use one of these methods and then apply EventMachine methods to it. Here's a subset of a server class:
class PulseDev < ApiMethods
def base_uri
"http://myserver.com/api"
end
end
And an action in the methods class:
Class ApiMethods
def get_json_api_post_response(url, post_obj={})
http = EM::Synchrony.sync EventMachine::HttpRequest.new(self.base_uri+"#{url}").post(:body => post_obj)
process_response self.class.post(url, :body => post_obj).body
end
def process_response(result)
response = ActiveSupport::JSON.decode(result)
if response["code"].to_i == 200
ToolResult.new(true, response["result"], 200)
else
ToolResult.new(false, response["result"], response["code"])
end
end
end
Class ToolResult < Struct.new(:success, :result, :code)
end
And my invocation of it in the controller:
http = ApiMethods::Dev.new.get_json_api_post_response('/handshake', #j)
OK, my error is undefined method `post' for ApiMethods::PulseDev:Class
and it points to the post in my get_json_api_post_response method.
My question is: I get that it's within the context of the ApiMethods::Dev which is why self.base_uri works but how should I handle the post inside that process_response method so that it's tied to EventMachine? Is there something about method chaining I'm not seeing? In the error output I can verify that http is showing the EventMachine object so the new method seems to be working. Thanks for your time, sam

The answer is to look more carefully at the error msg. The process_response method is the one actually calling the EventMachine method and processing its :body. So it was written with an unneeded call.

Related

How do I make a class conditionally return one of two other classes?

I have a design problem.
I'm writing a REST client in ruby. For reasons beyond my control, it has to extend another gem that uses my networks zookeeper instance to do service lookup. My client takes a user provided tier, and based on that value, queries the zookeeper registry for the appropriate service url.
The problem is that I also need to be able to run my client against a locally running version of the service under test. When the service is running locally, zookeeper is obviously not involved, so I simply need to be able to make GET requests against the localhost resource url.
When a user instantiates my gem, they call something like:
client = MyRestClient.new(tier: :dev)
or in local mode
client = MyRestClient.new(tier: :local)
I would like to avoid conditionally hacking the constructor in MyRestClient (and all of the GET methods in MyRestClient) to alter requests based on :local vs. :requests_via_the_zk_gem.
I'm looking for an elegant and clean way to handle this situation in Ruby.
One thought was to create two client classes, one for :local and the other for :not_local. But then I don't know how to provide a single gem interface that will return the correct client object.
If MyClient has a constructor that looks something like this:
class MyClient
attr_reader :the_klass
def initialize(opts={})
if opts[:tier] == :local
#the_klass = LocalClass.new
else
#the_klass = ZkClass.new
end
#the_klass
end
end
then I end up with something like:
test = MyClient.new(tier: :local)
=> #<MyClient:0x007fe4d881ed58 #the_klass=#<LocalClass:0x007fe4d883afd0>>
test.class
=> MyClient
test.the_klass.class
=> LocalClass
those who then use my gem would have to make calls like:
#client = MyClient.new(tier: :local)
#client.the_klass.get
which doesn't seem right
I could use a module to return the appropriate class, but then I'm faced with the question of how to provide a single public interface for my gem. I can't instantiate a module with .new.
My sense is that this is a common OO problem and I just haven't run into it yet. It's also possible the answer is staring me in the face and I just haven't found it yet.
Most grateful for any help.
A common pattern is to pass the service into the client, something like:
class MyClient
attr_reader :service
def initialize(service)
#service = service
end
def some_method
service.some_method
end
end
And create it with:
client = MyRestClient.new(LocalClass.new)
# or
client = MyRestClient.new(ZkClass.new)
You could move these two into class methods:
class MyClient
self.local
new(LocalClass.new)
end
self.dev
new(ZkClass.new)
end
end
And instead call:
client = MyRestClient.local
# or
client = MyRestClient.dev
You can use method_missing to delegate from your client to the actual class.
def method_missing(m, *args, &block)
#the_class.send(m, *args, &block)
end
So whenever a method gets called on your class that doesn't exist (like get in your example) it wil be called on #the_class instead.
It's good style to also define the corresponding respond_to_missing? btw:
def respond_to_missing?(m, include_private = false)
#the_class.respond_to?(m)
end
The use case you are describing looks like a classic factory method use case.
The common solution for this is the create a method (not new) which returns the relevant class instance:
class MyClient
def self.create_client(opts={})
if opts[:tier] == :local
LocalClass.new
else
ZkClass.new
end
end
end
And now your usage is:
test = MyClient.create(tier: :local)
=> #<LocalClass:0x007fe4d881ed58>
test.class
=> LocalClass

private method `new' called for MyReminderMailer:Class

In a controller, i have:
mailer = MyReminderMailer.new
the mailer looks like this:
class MyReminderMailer < ActionMailer::Base
def change_email
mail(
from: default_from,
to: default_to,
subject: "..."
)
end
def default_from
return '...'
end
def default_to
return '...'
end
end
but got error: private method `new' called for MyReminderMailer:Class
ActionMailer::Base has a rather goofy and unintuitive API. Much like controllers, you never explicitly create instances of your mailers. Instead, you interact with them as classes. new is marked private in ActionMailer::Base, and method calls on the class are subsequently routed through method_missing to a new instance of itself. Like I said, unintuitive.
Have a look at the guides and api docs for more information on the correct usage of ActionMailer.
Ruby does not allow to call private method in normal way.
You can call it with send method
SomeClass.send :method_name
#in your case
MyReminderMailer.send :new
And you don't need ActionMailer object.
To send mail just use the method as like class method.
MyReminderMailer.change_email.deliver
Hope this can help you.

How to distinguish between the Sinatra request object and the Rack Test request method?

I have a method that runs in the Sinatra app scope that checks to see if the request is secure:
secure_request?
request.env[ 'HTTPS' ] == 'on'
end
This works fine, but when I call it from another class that does not share the Sinatra app scope, it attempts to make an Rack Test request, raising an error: wrong number of arguments (0 for 1).
So, is there a way to specify the Sinatra app request explicitly, such as self.request or app.request?
Calling a request method from another class smells like poor code design, tightly coupling that other class to your app. Where is secure_request? defined? Is it a helper?
I would personally call a method from Sinatra to that other class and pass in the request value, instead of having that other method poll to find out. For example:
class OtherClass
def some_method( opts={} )
if opts[:secure]
# …
else
# …
end
end
end
class MyApp < Sinatra::Application
helpers do
secure_request?
request.env[ 'HTTPS' ] == 'on'
end
end
get '/' do
#otherclass.some_method( secure: secure_request? )
end
end

ruby, no method error

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.

How does rescue_action in Rails3 work?

I would like to implement this
class SecurityTransgression < StandardError; end
def create
raise SecurityTransgression unless ...
end
class ApplicationController < ActionController::Base
def rescue_action(e)
case e
when SecurityTransgression
head :forbidden
end
end
end
from the this blogpost.
The problem is it does not work. I dont see a forbidden page but standard Rails error page "SecurityViolation in MyController#action". I digged that some rescue_action methods works only in the production mode. I tried that and it is the same. No change.
My question: is there any good documentation of the rescue_action method (and others)? Does this work under Rails 3.0? Because it seems this is some old
Take a look at rescue_from at the API documentation.
The rescue_action method is normally called internally with the #_env hash being passed as a parameter. The method is expecting the Exception instance to exist within the "action_dispatch.rescue.exception" key.
If you must use the rescue_action method directly, you can do the following:-
#_env[ "action_dispatch.rescue.exception" ] = exception
rescue_action( #_env )
or even more simple:-
rescue_action( { "action_dispatch.rescue.exception" => exception } )

Resources