I have a simple class:
class Repository
class << self
def find(id)
...
end
end
end
It is called like this throughout our app:
thing = Repository.find("abc")
We are in a Sinatra/rack environment. During the request phase we do something like this:
env['org'] = 'acme'
What I'd like to do is be able to get access to 'acme' from inside the class Repository, without having to explicitly pass it in. There are so many calls to this class all over the place that it would be a pain to pass in the value each time through the find method e.g., find(id,org = nil). I thought maybe there's a way to include the rack gem in Repository, and get at it that way, but so far no luck. Global variables are out - has to be scoped to the request.
Is it possible to do something like this?
Personally, I think having a variable that changes like that inside a class method is asking for trouble, it breaks the Law of Demeter by reaching across boundaries. Instead, I'd wrap it in a Sinatra helper which then passes the second argument by default.
helpers do
def find( s )
Repository.find( s, env['org'] )
end
end
and modify the Repository's find method to take the second argument.
Related
My question has a couple layers to it so please bear with me? I built a module that adds workflows from the Workflow gem to an instance, when you call a method on that instance. It has to be able to receive the description as a Hash or some basic data structure and then turn that into something that puts the described workflow onto the class, at run-time. So everything has to happen at run-time. It's a bit complex to explain what all the crazy requirements are for but it's still a good question, I hope. Anyways, The best I can do to be brief for a context, here, is this:
Build a class and include this module I built.
Create an instance of Your class.
Call the inject_workflow(some_workflow_description) method on the instance. It all must be dynamic.
The tricky part for me is that when I use public_send() or eval() or exec(), I still have to send some nested method calls and it seems like they use 2 different scopes, the class' and Workflow's (the gem). When someone uses the Workflow gem, they hand write these method calls in their class so it scopes everything correctly. The gem gets to have access to the class it creates methods on. The way I'm trying to do it, the user doesn't hand write the methods on the class, they get added to the class via the method shown here. So I wasn't able to get it to work using blocks because I have to do nested block calls e.g.
workflow() do # first method call
# first nested method call. can't access my scope from here
state(:state_name) do
# second nested method call. can't access my scope
event(:event_name, transitions_to: :transition_to_state)
end
end
One of the things I'm trying to do is call the Workflow#state() method n number of times, while nesting the Workflow#event(with, custom_params) 0..n times. The problem for me seems to be that I can't get the right scope when I nest the methods like that.
It works just like I'd like it to (I think...) but I'm not too sure I hit the best implementation. In fact, I think I'll probably get some strong words for what I've done. I tried using public_send() and every other thing I could find to avoid using class_eval() to no avail.
Whenever I attempted to use one of the "better" methods, I couldn't quite get the scope right and sometimes, I was invoking methods on the wrong object, altogether. So I think this is where I need the help, yeah?
This is what a few of the attempts were going for but this is more pseudo-code because I could never get this version or any like it to fly.
# Call this as soon as you can, after .new()
def inject_workflow(description)
public_send :workflow do
description[:workflow][:states].each do |state|
state.map do |name, event|
public_send name.to_sym do # nested call occurs in Workflow gem
# nested call occurs in Workflow gem
public_send :event, event[:name], transitions_to: event[:transitions_to]
end
end
end
end
end
From what I was trying, all these kinds of attempts ended up in the same result, which was my scope isn't what I need because I'm evaluating code in the Workflow gem, not in the module or user's class.
Anyways, here's my implementation. I would really appreciate it if someone could point me in the right direction!
module WorkflowFactory
# ...
def inject_workflow(description)
# Build up an array of strings that will be used to create exactly what
# you would hand-write in your class, if you wanted to use the gem.
description_string_builder = ['include Workflow', 'workflow do']
description[:workflow][:states].each do |state|
state.map do |name, state_description|
if state_description.nil? # if this is a final state...
description_string_builder << "state :#{name}"
else # because it is not a final state, add event information too.
description_string_builder.concat([
"state :#{name} do",
"event :#{state_description[:event]}, transitions_to: :#{state_description[:transitions_to]}",
"end"
])
end
end
end
description_string_builder << "end\n"
begin
# Use class_eval to run that workflow specification by
# passing it off to the workflow gem, just like you would when you use
# the gem normally. I'm pretty sure this is where everyone's head pops...
self.class.class_eval(description_string_builder.join("\n"))
define_singleton_method(:has_workflow?) { true }
rescue Exception => e
define_singleton_method(:has_workflow?) { !!(puts e.backtrace) }
end
end
end
end
# This is the class in question.
class Job
include WorkflowFactory
# ... some interesting code for your class goes here
def next!
current_state.events.#somehow choose the correct event
end
end
# and in some other place where you want your "job" to be able to use a workflow, you have something like this...
job = Job.new
job.done?
# => false
until job.done? do job.next! end
# progresses through the workflow and manages its own state awareness
I started this question off under 300000 lines of text, I swear. Thanks for hanging in there! Here's even more documentation, if you're not asleep yet.
module in my gem
I'm trying to implement a Facade in idiomatic Ruby while coming from Java. I can see that Rails' ActiveRecord is fond of using class methods for things like find_by(criteria) and does not use Repository pattern for that task.
My Facade wraps a specific webservice with several methods. My original idea was to make it's API similar to ActiveRecord (learning by imitation):
class MyEntity
# ....
def get_name
#loaded_name + #loaded_surname
end
def delete
#entity_access_service.delete(#id)
end
def save
#entity_access_service.save(#id, #loaded_name , #loaded_surname)
end
def self.find(id)
data = #entity_access_service.get_data_for(id)
MyEntity.new(data) #Or whatever way to populate my entity
end
end
This, in theory, would work great:
e = MyEntity.find(10)
p e.get_name
e.delete
Or:
e = MyEntity.new(some stuff)
e.save
Question:
For save and delete instance methods to work, I need to somehow get an instance of EntityAccessService. This instance should be mockable to test it in isolated environment. What is the correct way to do it?
I'm expecting my tests to look as simple as possible and without some weird hacks, as what I'm trying to implement seems fairly trivial.
I have thought of several options to do that:
Having a class-level variable holding entity_access_service used by all of the entities created in application. In this case, where should I initialize this field? For example:
class MyEntity
##entity_access_service = nil
end
# Somewhere else (where?):
MyEntity.entity_access_service = MyEntityService.new(some_params_from_env)
This way, in my tests I would have to initialize/mock it at start.
Similar to 1 but initialize it in the class. This looks weird, especially if I know that my tests do not have required ENV params populated at all.
Have an extra constructor/attribute to set the entity_service. This won't work, as save would not have this field initialized.
Create a Repository class. This would work pretty ok, but seems to be not what Ruby people do.
Following ActiveRecord's example, you can create a method on your class itself, or on the base class from which your other classes are derived.
ActiveRecord provides a method ActiveRecord::Base.connection which returns the connection object which all models use to access the database. You can do something similar:
class MyEntity
....
def self.entity_access_service
# return your service object
end
def self.find(id)
MyEntity.entity_access_service.get_data_for(id)
MyEntity.new(data) # Or whatever way to populate my entity
end
def save()
MyEntity.entity_access_service.save(#id, #loadedName, #loadedSurname)
end
end
As far as initialization goes, you either have to have a initialization step in your app (and test suite) where service credentials are read from config files and passed into your MyEntity object, or your entity_access_service method can lazily create the object it returns on first access using a very common Ruby idiom:
def self.entity_access_service
#entity_access_service || = # build entity_access_service object
end
Note that, by wrapping your class-level instance variables in class-level accessor methods, you can avoid the use of ## which is a recommended best practice.
I've been bashing my head against this for about three days now. I've created a class that models html pages and tells cucumber step definitions where to populate form data:
class FlightSearchPage
def initialize(browser, page, brand)
#browser = browser
#start_url = page
#Get reference to config file
config_file = File.join(File.dirname(__FILE__), '..', 'config', 'site_config.yml')
#Store hash of config values in local variable
config = YAML.load_file config_file
#brand = brand #brand is specified by the customer in the features file
#Define instance variables from the hash keys
config.each do |k,v|
instance_variable_set("##{k}",v)
end
end
def method_missing(sym, *args, &block)
#browser.send sym, *args, &block
end
def page_title
#Returns contents of <title> tag in current page.
#browser.title
end
def visit
#browser.goto(#start_url)
end
def set_origin(origin)
self.text_field(#route[:attribute] => #route[:origin]).set origin
end
def set_destination(destination)
self.text_field(#route[:attribute] => #route[:destination]).set destination
end
def set_departure_date(outbound)
self.text_field(#route[:attribute] => #date[:outgoing_date]).set outbound
end
# [...snip]
end
As you can see, I've used instance_variable_set to create the variables that hold the references on the fly, and the variable names and values are supplied by the config file (which is designed to be editable by people who aren't necessarily familiar with Ruby).
Unfortunately, this is a big, hairy class and I'm going to have to edit the source code every time I want to add a new field, which is obviously bad design so I've been trying to go a stage further and create the methods that set the variable names dynamically with define_method and this is what's kept me awake until 4am for the last few nights.
This is what I've done:
require File.expand_path(File.dirname(__FILE__) + '/flight_search_page')
class SetFieldsByType < FlightSearchPage
def text_field(config_hash)
define_method(config_hash) do |data|
self.text_field(config_hash[:attribute] => config_hash[:origin]).set data
end
end
end
The idea is that all you need to do to add a new field is add a new entry to the YAML file and define_method will create the method to allow cucumber to populate it.
At the moment, I'm having problems with scope - Ruby thinks that define_method is a member of #browser. But what I want to know is: is this even feasible? Have I totally misunderstood define_method?
Do you mean that you'd expect to see the requires and file loads outside the class definition?
No, inside the class definition. Ruby class declarations are just code that execute in the order it's seen. Things like attr_accessor are just class methods that happen to do things to the class being defined, as it's being defined. This seems like what you want to do.
In your case you'd read the YAML file instead, and run your own logic to create accessors, build any backing data required, etc. I don't totally grok the usecase, but it doesn't sound unusual or difficult--yet.
That said, how much "convenience" do you get by putting these definitions in a YAML file? Consider something like I did once to create page instances I used to drive Watir:
class SomePage < HeavyWatir
has_text :fname # Assumed default CSS accessor pattern
has_text :whatever, accessor: 'some accessor mechanism', option: 'some other option'
end
The has_xxx were class methods that created instance variable accessors (just like attr_accessor does), built up some other data structures I used to make sure all the things that were supposed to be on the page actually were, and so on. For example, very roughly:
page = SomePage.new
page.visit
if page.missing_fields?
# Do something saying the page isn't complete
end
It sounds like you want something vaguely similar: you have a bunch of "things" you want to give to the class (or a sub-class, or you could mix it in to an arbitrary class, etc.)
Those "things" have additional functionality, that functionality works in multiple ways, like:
Things-that-happen-during-definition
E.g., has_text adds the name to a class instance hash ofthe page's metadata, like field names.
Things-that-happen-during-usage
E.g., when fname= is called set a flag in the instance's metadata saying the setter was called.
This is an appropriate case for metaprogramming, but it looks like you're going about it the wrong way.
First of all, is there going to be a different config file for each instance of FlightSearchPage or just one config file that controls all pages? It looks like you're loading the same config file regardless of the arguments to initialize so I'm guessing your case is the former.
If that is so, you need to move all of your metaprogramming code into the class (outside method definitions). I.e. when the class is defined, you want it to load the config file and then each instance is created based on that config. Right now you have it reloading the config file every time you create an instance, which seems incorrect. For example, define_method belongs to Module so it should appear in class scope, rather than in an instance method.
On the other hand, if you do want a different config for each instance, you need to move all of your metaprogramming code into the singleton class e.g. define_singleton_method rather than define_method.
I am in a position at the moment where I have a plugins folder where there there could be 1 or 100 plugins to be loaded.
Now the problem is, each plugin requires an instance of a class defined within the startup ruby file.
A really simplified example would be:
#startup.rb
def load_plugins
#... get each plugin file
require each_plugin
end
class MuchUsedClass
def do_something
#...
end
end
muchUsedInstance = MuchUsedClass.new
load_plugins
#some_plugin.rb
class SomePluginClass
def initialize(muchUsedInstance)
#muchUsedInstance = muchUsedInstance
end
def do_something_with_instance
#muchUsedInstance.do_something
end
end
somePluginInstance = SomePluginClass.new(muchUsedInstance)
somePluginInstance.do_something_with_instance
The main problem is that when you call require, it doesn't have any clue about what has happened before it is being required. So I find it nasty making a global variable within the startup file just to satisfy all other required files, but it seems like one of the only ways to be able to pass some data down to an included file, I could also make a singleton class to expose some of this, but that also seems a bit nasty.
As I am still new to ruby and am still looking through the statically typed glasses, I will probably be missing a decent pattern to solve this, in C# I would opt for dependency injection and just hook everything up that way...
Your example code does not have a global variable. Global variables have names that start with $. The code as you wrote it won't work, because muchUsedInstance is just a local variable and will not be shared between different Ruby files.
If you are not going to change the instance ever, you could easily store it as a constant:
MuchUsedInstance = MuchUsedClass.new
You could store it as a nested constant inside the class:
MuchUsedClass::Instance = MuchUsedClass.new
You could store it as an instance variable inside the class object, with a getter method that automatically creates it if it isn't there already:
def MuchUsedClass.instance
#instance ||= MuchUsedClass.new
end
I have a curiosity question. If I have a ruby class and then I dynamically add class methods, class variables, etc. to it during execution is there anyway for me to save the altered class definition so that next time I start my application I can use it again?
There is no built-in way to do this. Marshal can't save methods. If these methods and variables are generated in some systematic way, you could save the data necessary for the class to recreate them. For example, if you have a make_special_method(purpose, value) method that defines these methods, create an array of the arguments you need to pass to these methods and read it in when you want to reconstitute the state of the class.
Depending on what exactly you mean, there are a couple of ways to go about this.
The simplest case is the one where you've added variables or methods to an already-existing class, as in this example:
class String
def rot13
return self.tr('a-z', 'n-za-m')
end
end
Here we've added the rot13 method to class String. As soon as this code is run, every String everywhere in your program will be able to #rot13. Thus, if you have some code that needs rot13-capable strings, you just ensure that the code above is run before the code in question, e.g. by putting the rot13 code in a file someplace and require()ing it. Very easy!
But maybe you've added a class variable to a class, and you want to preserve not just its existence but its value, as in:
class String
##number_of_tr_calls_made = 0
# Fix up #tr so that it increments ##number_of_tr_calls_made
end
Now if you want to save the value of ##number_of_tr_calls_made, you can do so in the same way you would with any other serializable Ruby value: via the Marshal library. Also easy!
But something in the way you phrased your question makes me suspect that you're doing something like this:
greeting = "Hello"
class <<greeting
def rot13
return self.tr('a-z', 'n-za-m')
end
end
encrypted_greeting = greeting.rot13
This is very different from what we did in the first example. That piece of code gave every String in your program the power to rot13 itself. This code grants that power to only the object referred to by the name 'greeting'. Internally, Ruby does this by creating an anonymous Singleton subclass of String, adding the rot13 method to it, and changing greeting's class to that anonymous subclass.
The problem here is that Singletons can't be Marshal'd (to see why, try to figure out how to maintain the Singleton invariant when any call to Marshal.load can generate copies of extant Singleton objects). Now greeting has a Singleton in its inheritance hierarchy, so if you want to save and load it you are hosed. Make a subclass instead:
class HighlySecurableString < String
def rot13
return self.tr('a-z', 'n-za-m')
end
end
greeting = HighlySecurableString.new("hello")
Simply marshalling the object (as others have said) wont work. Lets look at an example. Consider this class:
class Extras
attr_accessor :contents
def test
puts "This instance of Extras is OK. Contents is: " + #contents.to_s
end
def add_method( name )
self.class.send :define_method, name.to_sym do
puts "Called " + name.to_s
end
end
end
Now lets write a program which creates an instance, adds a method to it and saves it to disk:
require 'extras'
fresh = Extras.new
fresh.contents = 314
fresh.test # outputs "This instance of Extras is OK. Contents is: 314"
fresh.add_method( :foo )
fresh.foo # outputs "Called foo"
serial = Marshal.dump( fresh )
file = File.new "dumpedExample", 'w'
file.write serial
So we can call the normal method 'test' and the dynamic method 'foo'. Lets look at what happens if we write a program which loads the instance of Example which was saved to disk:
require 'extras'
file = File.new 'dumpedExample', 'r'
serial = file.read
reheated = Marshal.load( serial )
reheated.test # outputs "This instance of Extras is OK. Contents is 314"
reheated.foo # throws a NoMethodError exception
So we can see that while the instance (including the values of member variables) was saved the dynamic method was not.
From a design point of view it's probably better to put all your added code into a module and load that into the class again when you next run the program. We'd need a good example of how you might want to use it though to really know this.
If you need extra information to recreate the methods then have the module save these as member variables. Implement included in the module and have it look for these member variables when it is included into the class.
http://ruby-doc.org/core/classes/Marshal.html
You're editing the class on the fly, and you want to save that? You could try using the Marshal module, it'll allow you to save objects to a file, and read them back in memory dynamically.