I'm upgrade a codebase from 1.8 to 1.9. I'm encountering a couple places in my code (only specs, not sure if that's a coincidence) where there are problems with blocks that don't have a variable. Giving them a dummy variable fixes the problem. Here's an example:
In a factory girl factory, this works under 1.8:
Factory.define :thing do |t|
t.price { 1 - 0.01*rand(10) }
end
Under 1.9, rand(10) returns nil. Very odd. I was racking my brain for why the environment would be different inside the block. The thing is, rand isn't even from the standard library -- it's from the core language! So there isn't really a way that the environment would make a difference.
Then I remembered that some other places in my specs were breaking because of no-variable blocks, so on a whim I threw one in there, and lo and behold it worked.
Factory.define :thing do |t|
t.price { |dummy| 1 - 0.01*rand(10) }
end
What is going on here?
In recent versions of factory_girl, defining an attribute without a block argument uses instance_eval, and it assumes that bare method calls are looking for previous attribute definitions, methods on your model, or syntax methods like "create" or "build."
In order to make attributes like "open" or "file" work correctly, the proxy object undefines most private methods including "rand." This means that you need to use "Kernel.rand" instead of just "rand."
You can see the relevant source here: https://github.com/thoughtbot/factory_girl/blob/master/lib/factory_girl/evaluator.rb#L16
Related
Most of the Factorybot factories are like:
FactoryBot.define do
factory :product do
association :shop
title { 'Green t-shirt' }
price { 10.10 }
end
end
It seems that inside the ":product" block we are building a data structure, but it's not the typical hashmap, the "keys" are not declared through symbols and commas aren't used.
So my question is: what kind of data structure is this? and how it works?
How declaring "association" inside the block doesn't trigger a:
NameError: undefined local variable or method `association'
when this would happen on many other situations. Is there a subject in compsci related to this?
The block is not a data structure, it's code. association and friends are all method calls, probably being intercepted by method_missing. Here's an example using that same technique to build a regular hash:
class BlockHash < Hash
def method_missing(key, value=nil)
if value.nil?
return self[key]
else
self[key] = value
end
end
def initialize(&block)
self.instance_eval(&block)
end
end
With which you can do this:
h = BlockHash.new do
foo 'bar'
baz :zoo
end
h
#=> {:foo=>"bar", :baz=>:zoo}
h.foo
#=> "bar"
h.baz
#=> :zoo
I have not worked with FactoryBot so I'm going to make some assumptions based on other libraries I've worked with. Milage may vary.
The basics:
FactoryBot is a class (Obviously)
define is a static method in FactoryBot (I'm going to assume I still haven't lost you ;) ).
Define takes a block which is pretty standard stuff in ruby.
But here's where things get interesting.
Typically when a block is executed it has a closure relative to where it was declared. This can be changed in most languages but ruby makes it super easy. instance_eval(block) will do the trick. That means you can have access to methods in the block that weren't available outside the block.
factory on line 2 is just such a method. You didn't declare it, but the block it's running in isn't being executed with a standard scope. Instead your block is being immediately passed to FactoryBot which passes it to a inner class named DSL which instance_evals the block so its own factory method will be run.
line 3-5 don't work that way since you can have an arbitrary name there.
ruby has several ways to handle missing methods but the most straightforward is method_missing. method_missing is an overridable hook that any class can define that tells ruby what to do when somebody calls a method that doesn't exist.
Here it's checking to see if it can parse the name as an attribute name and use the parameters or block to define an attribute or declare an association. It sounds more complicated than it is. Typically in this situation I would use define_method, define_singleton_method, instance_variable_set etc... to dynamically create and control the underlying classes.
I hope that helps. You don't need to know this to use the library the developers made a domain specific language so people wouldn't have to think about this stuff, but stay curious and keep growing.
In my n00bish understanding, 1 is class Fixnum.
puts 1.class.name
send() allows you to call a method within the class. So 1.send() should call a method of class Fixnum.
Finally, what's in the parenthesis specifies which method to call. In this case, I would be calling the hour method of the Fixnum class.
But there is no "hour" method of Fixnum, as least from what I can see here. Instead, it seems to take the Fixnum object and multiply by the number of seconds in an hour to give the number of seconds in [object] number of :hours. And...I don't follow - how do you think about why this works the way it does?
Ruby has a very flexible run-time environment where classes and objects can have methods added or removed at any time for any reason by any bit of code in the program.
This ability to mutate existing classes and add in new functionality is called "monkey patching", though if done more formally it's usually described as "core extensions", that is, extensions to core Ruby classes. Rails has a lot of these and hours is one of them.
Looking in the class documentation is not necessarily the most reliable way of finding out where a method is coming from. In most cases the best approach is to simply ask, as Ruby does have great support for reflection, where code can interrogate code about things:
1.method(:hours)
That returns a Method object that describes the method in question. One of the most useful parts of that is this:
1.method(:hours).source_location
That tells you where the method's defined. It'll lead you to something like active_support/core_ext/numeric/time.rb which means this is an ActiveSupport thing, which it is. These extensions are documented on the Rails site if you're curious about the details.
Keep in mind a simple value like 1 gets methods from all of its ancestor classes, plus any module extensions applied to it (mix-ins) as well as any other things that have been added on in a more ad-hoc fashion.
If you ask where that gets methods from:
1.class.ancestors
That's an awfully long list in modern Rails.
This is rails extension (read: monkeypatch) for Numeric class.
Rails has a numeric monkey patch available which will convert units like 1.hour, 1.minute, or even 1.day to a format friendly for working with the Ruby class of Time.
So without this numeric helper, something like this:
snapshot_of_time = Time.now
snapshot_of_time + 1
# => Just returns the time when the variable was defined, plus 1 second.
# To move it forward an hour, I'd have to remember an hour was 3600 seconds.
Whereas, with this Numeric class method added:
snapshot_of_time = Time.now
snapshot_of_time + 1.hour
# => Predictably provides you with a time that's 1 hour in the future
# of your snapshot!
Simply put, you're either running some sort of method extension in ruby, or in a rails console. This helper method is not included in Ruby by default as of version 2.4.0
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 would like to shadow ENV within a templating method, so that I can raise an error if keys are requested which are not present in the real ENV. Obviously I don't want to shadow the constant elsewhere - just within a specific method (specific binding). Is this even possible?
Explainer: - I know about the existence of Hash#fetch and I use it all the time and everywhere. However, I want to use this in an ERB template generating a config file. This config file is likely to be touched by more people than usual, and not everyone is familiar with the Ruby behavior of returning a nil for a missing Hash key. I am also working on a system where, of late, configuration mishaps (or straight out misconfigurations, or misunderstandings of a format) caused noticeable production failures. The failures were operator error. Therefore, I would like to establish a convention, within that template only, that would cause a raise. Moreover, I have a gem, strict_env, that does just that already - but you have to remember to use STRICT_ENV instead of just ENV, and every "you have to" statement for this specific workflow, in this specific case, raises a red flag for me since I want more robustness. I could of course opt for a stricter templating language and use that language's logic for raising (for example, Mustache), but since the team already has some familiarity with ERB, and Rails endorses ERB-templated-YML as a viable config approach (even though you might not agree with that) it would be nice if I could stick to that workflow too. That's why I would like to alter the behavior of ENV[] locally.
ERB#result takes an optional binding:
require 'erb'
class Foo
ENV = { 'RUBY_VERSION' => '1.2.3' }
def get_binding
binding
end
end
template = "Ruby version: <%= ENV['RUBY_VERSION'] %>"
ERB.new(template).result
#=> "Ruby version: 2.1.3"
b = Foo.new.get_binding
ERB.new(template).result b
#=> "Ruby version: 1.2.3"
You can use ENV.fetch(key) to raise when the key is not present.
Other than that you could create a class and delegate to ENV, such as:
class Configuration
def self.[](key)
ENV.fetch(key)
end
end
But raising an error from #fetch instead #[] is more Ruby-like since this is the same behaviour for Hash.
Finally you could monkey patch ENV, but this is usually not a good thing to do:
def ENV.[](key)
fetch(key)
end
As far as I know you can't use refinements to localise this monkey patch because ENV is an object, not a class and its class is Object.
What is the safe way to redefine methods in core classes like File, String etc. I'm looking for implementing something similar to the Java Security Manager in (J)Ruby.
I'm looking for a way to redefine a method by first seeing which class/script has called this method and if that class/script belong to a list of some blacklisted classes (that I keep track of) I want to raise an exception, If the calling class belong to a non-blacklisted class then allow the operation. something like:
class String
alias_method :old_length, :length
def length
if(#nowHowDoIGetTheCallingClass)
raise "bad boy"
else
old_length
end
end
I tried this in JRuby, but this works only alternatively. One time the new length method is called and next time the old length method is called. I guess the alias doesn't work properly in JRuby! >.<
If something works sometimes, but not at other times, it's more likely to be your code that's the problem, not JRuby. Select isn't broken