I am having a hard time understanding an error with ruby's open classes. Overriding a method in test environment (rails) causes an invalid arguments error on initialize. For some reason, overriding the new method solved the problem. Why?
My class:
require 'adwords_api'
require 'adwords_config'
require 'scanf'
class AdwordsHelper
attr_accessor :adwords_id
attr_accessor :campaign_id
attr_accessor :adgroup_id
attr_accessor :invalid_ops
##RETRY_INTERVAL = 3
##RETRIES_COUNT = 500
##config = 'adwords_api.yml'
def initialize(args = {})
self.adwords_id = AdwordsConfig.config[:master_account]
return unless args.is_a?(Hash)
args.each do |k,v|
instance_variable_set("##{k}", v) unless v.nil?
end
end
def set_budget(budget)
#api = get_adwords_api
budget_id = get_budget_id
service = #api.service(:BudgetService)
operation = {
:operator => 'SET',
:operand => {
:budget_id => budget_id,
:amount => {
:micro_amount => budget
}
}
}
service.mutate([operation])
end
end
Now in the test, I want to override one of the methods, because I don't want there to be communication with the actual adwords servers.
require "test_helper"
# FIXME override this in another file eventually
class AdwordsHelper
def set_budget(budget)
true
end
end
Calling
AdwordsHelper.new(campaign_id: 1)
gives an invalid arguments 1 for 0 error.
Now, overriding the new function, solves the error.
class AdwordsHelper
def self.new(*args, &block)
obj = allocate
obj
end
def set_budget(budget)
true
end
end
Is overriding the method set_budget causing other quirks?
Try
AdwordHelper.class_eval do
def set_budget(budget)
true
end
end
class AdwordHelper will define the class when the original AdwordHelper is not loaded already, in which case the only method that exists in your class is set_budget. It wouldn't know about the initialize with one argument.
Whereas, if you use class_eval, it tries to load the file containing AdwordHelper class and then add the method set_budget to the class.
Related
I am working with Elasticsearch persistence model, and have some common methods for each index.
Given an Events Index, I have a service class where some methods are defined, the same goes for other n indexes built of their models.
class EventSearchService
class << self
def with_index(index_name)
old_repository = repository
#repository = EventSearchService::ElasticsearchEventRepository.new(index_name: index_name)
yield
ensure
#repository = old_repository
end
def index_name
repository.index_name
end
def index_all(event_documents)
return unless event_documents.present?
actions = event_documents.map do |e|
{ index: { _index: index_name, _id: e.id, _type: "_doc", data: e.to_hash }}
end
repository.client.bulk(body: actions)
end
protected
def repository
#repository ||= EventSearchService::ElasticsearchEventRepository.new
end
end
end
My problem is that I have ended up with n files with the same class methods. When I try to extract it out to an abstract class directly, I get an error whose investigation reaches me to a point that singleton classes can't be inherited.
After searching for some answers, I followed this thread and I tried to DRY it up
require 'forwardable'
require 'singleton'
class ElasticsearchService
include Singleton
class << self
extend Forwardable
def_delegators(
:with_index,
:index_name,
:index_all,
:repository
)
end
def with_index(index_name)
old_repository = repository
#repository = search_repository.new(index_name: index_name)
yield
ensure
#repository = old_repository
end
def index_name
repository.index_name
end
def index_all(documents)
return unless documents.present?
actions = documents.map do |d|
{ index: { _index: index_name, _id: d.id, _type: "_doc", data: e.to_hash }}
end
repository.client.bulk(body: actions)
end
def search_repository
fail "Needs to be overriden"
end
protected
def repository
#repository ||= search_repository.new
end
end
And I include it as
class EventSearchService < ElasticsearchService
def search_repository
EventSearchService::ElasticsearchEventRepository
end
end
I have redacted the code to keep it small, simple, and related to the cause, but wanted to show different aspects of it. Sorry if it's too long a read.
The error I get is:
`<class:ElasticsearchService>': undefined local variable or method `' for ElasticsearchService:Class (NameError)
This one is very sneaky. There are some non-ASCII space characters inside your code which the ruby interpreter is recognizing as the name of a method that's being called.
I threw your code inside my terminal, getting precisely the same error as you, but after writing it by hand and executing method by method, did not get it.
Found a conversor online and after copy/pasting your code (here's the link to the one I used), the code ran without that error.
So formatting the file properly should do the trick with that particular error you're experiencing.
I have a case, when the text from the DB field should be "evaled" in the sandbox mode - with whitelist of methods and constants, allowed to invoke.
Gem https://github.com/tario/shikashi fits to this perfectly, but it seems to me, that it's abandoned.
I even can't use run Basic Example 2 (only Basic Example 1 works fine):
require "rubygems"
require "shikashi"
include Shikashi
def foo
# privileged code, can do any operation
print "foo\n"
end
s = Sandbox.new
priv = Privileges.new
# allow execution of foo in this object
priv.object(self).allow :foo
# allow execution of method :times on instances of Fixnum
priv.instances_of(Fixnum).allow :times
#inside the sandbox, only can use method foo on main and method times on instances of Fixnum
s.run(priv, "2.times do foo end")
Because it fails with an Error Cannot invoke method foo on object of class Object (SecurityError)
This gem uses another gem evalhook that looks for me complicated in order to fix the issue. There are another gems like this one but they are even more abandoned.
As far as I understood using $SAFE is not a good idea, because it has vulnerabilities.
Are there another approaches for such feature? Maybe manipulating with Binding object?
My problem was not so hard to solve without any gems. The idea is that you have whitelist of methods and constants WHITELIST and this class checks, if all methods and constants are in the whitelist
# gem install 'parser'
require 'parser/current'
class CodeValidator
attr_reader :errors, :source
WHITELIST = {:send => [:puts, :+, :new], :const => [:String]}
class Parser::AST::Node
def value
return children[1] if [:send, :const].include? type
fail NotImplementedError
end
end
def initialize(source)
#errors = []
#source = source
end
def valid?
!insecure_node?(root_node)
end
private
def exclude_node?(node)
blacklisted_node_types.include?(node.type) && !WHITELIST[node.type].include?(node.value)
end
def blacklisted_node_types
WHITELIST.keys
end
def insecure_node?(node)
return !!add_error_for_node(node) if exclude_node?(node)
node.children.each { |child_node| return true if child_node.class == Parser::AST::Node && insecure_node?(child_node) }
false
end
def root_node
#root_node ||= Parser::CurrentRuby.parse source
end
def add_error_for_node(node)
errors << "#{node.type} not allowed: #{node.value}"
end
end
c = CodeValidator.new("s = 'hello ' + String.new('world'); puts s.inspect")
p c.valid? # => false
p c.errors # => ["send not allowed: inspect"]
I have tried several way to rspec the 'to_type' function. The fact that it is inside the class means that only the class should be able to call it right? I've tried to include the Class in my rspec but the module "Format" is still not recognized. Any ideas how I can rspec this method 'to_type' from the module?
class Loom::Lma < Loom::Base
module Format
STANDARD_FORMATS = {
1 => '0',
2 => '13.4',
}
def to_type(format)
# type is calculated here then return type
# for instance
return :date
end
module_function :to_type
end
def initialize()
#init stuff
end
def otherstuff()
#another function
end
end
RSPEC
it 'type should not be :date' do
include Loom::Lma
Format.to_type('some string format').should_not eq(:date)
end
Any ideas?
Are you sure you want to put that module into a class not the other way around?
Anyway, you can access to_type like this:
Loom::Lma::Format.to_type()
I'm trying to create a super class that will be extended. The super class will call a method that has to be implemented by the child class. The thing is, that method is called sometimes 3 blocks deep. In those blocks, I also refer to attributes of the class.
But, I get an error saying that there is no variable or method, and it's because the methods and variables are assumed to be from the block class.
This is how it looks like:
class SuperClass
attr_accessor :model
def initialize(model)
#model = model
end
def resources
s = Tire.search(get_index) do
query do
boolean do
must { term :model_id, model.id } #attr_accessor fails
must { all }
search_scope(self) #search_scope fails
end
end
sort do
sort_scope(self) #sort_scope fails
end
end
s.results
end
end
class SubClass < SuperClass
attr_accessor :params
def initialize(model, params)
#params = params
super(model)
end
def search_scope(boolean_query)
boolean_query.must { term field: params[:feild] }
#...
end
def sort_scope(sort_query)
sort_query.by :field, params[:sort_dir]
#...
end
end
search = SubClass.new(model, {})
results = search.resources # undefined method error as explained below
What I'm trying to achieve is calling the method search_scope and sort_scope (Implemented in child classes) that will set also set a few search and sort parameters. But I get undefined method 'search_scope' for #<Tire::Search::BooleanQuery:0x00000004fc9820>. As you can see, it's trying to call search_scope on the class of the block context. Same with the attr_accessor :model.
I know I can remedy this by doing
def resources
instance = self
# ...
end
And then calling instance.model and instance.search_scope, but this means my child classes have to define the instance in their own search_scope and sort_scope methods too.
I was wondering whether there is a better way to solving this?
For cruft-removal purposes I would like to log whenever a method from one of my AR models is called.
I can get get all those classes with something like this:
subclasses = [] ; ObjectSpace.each_object(Module) {|m| subclasses << m if m.ancestors.include? ActiveRecord::Base } ; subclasses.map(&:name)
But then I need a list of only the methods defined on those classes (instance and class methods), and a way to inject a logger statement in them.
The result would be the equivalent of inserting this into every method
def foo
logger.info "#{class.name} - #{__method__}"
# ...
end
def self.foo
logger.info "#{name} - #{__method__}"
# ...
end
How can I do that without actually adding it to every single method?
Some awesome meta perhaps?
If you want only the methods defined in the class you can do this:
>> Project.instance_methods
=> ["const_get", "validates_associated", "before_destroy_callback_chain", "reset_mocha", "parent_name", "inspect", "slug_normalizer_block", "set_sequence_name", "require_library_or_gem", "method_exists?", "valid_keys_for_has_and_belongs_to_many_association=", "table_name=", "validate_find_options_without_friendly", "quoted_table_name" (another 100 or so methods)]
Only the methods defined in your class
>> Project.instance_methods(false)
=> ["featured_asset", "category_list", "before_save_associated_records_for_slugs", "asset_ids", "primary_asset", "friendly_id_options", "description", "description_plain"]
You should be using Aspect Oriented Programming pattern for this. In Ruby Aquarium gem provides the AOP DSL.
Create a log_method_initializer.rb in config/initializers/ directory.
require 'aquarium'
Aspect.new(:around, :calls_to => :all_methods,
:in_types => [ActiveRecord::Base] ) do |join_point, object, *args|
log "Entering: #{join_point.target_type.name}##{join_point.method_name}"
result = join_point.proceed
log "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
result
end
Every method calls of classes inherited from ActiveRecord::Base will be logged.
You have
AR::Base.instance_methods
and
AR::Base.class_eval "some string"
so you can probably use them to put a header on every existing method.
For instance method call you can use this proxy pattern:
class BlankSlate
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
end
class MyProxy < BlankSlate
def initialize(obj, &proc)
#proc = proc
#obj = obj
end
def method_missing(sym, *args, &block)
#proc.call(#obj,sym, *args)
#obj.__send__(sym, *args, &block)
end
end
Example:
cust = Customer.first
cust = MyProxy.new(cust) do |obj, method_name, *args|
ActiveRecord::Base.logger.info "#{obj.class}##{method_name}"
end
cust.city
# This will log:
# Customer#city
This is inspired from: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
You will need to find a way to apply this pattern on ActiveRecord::Base object creation.
For Aquarium, seems like adding method_options => :exclude_ancestor_methods does the trick.
I had the stack too deep problem as well.
Source
http://andrzejonsoftware.blogspot.com/2011/08/tracing-aspect-for-rails-application.html