How do I get cucumber and pickle working with mongo_mapper, machinist, and machinist_mongo? - ruby

I would like to get machinist, machinist_mongo, mongo_mapper, cucumber and pickle to play nice together.
Currently I have machinist with all my blueprints configured and am using cucumber to do BDD. So far so good. My problem is I am having to write custom cucumber steps for all of my machinist blueprints. It is not really a problem per se, since it is not stopping me in my tracks, but as a .NET dev checking out rails, it feels really dirty to have to write a step for each blueprint whereas in .NET I could probably use reflection.
Is there any way I can get pickle's built in capture_model, capture_plural_factory, etc, to recognize my machinist blueprints?
I am pretty confident I have machinist configured and set up correctly, because when I use blueprintname.make, in a custom cucumber step, everything works out correctly.
Gem versions:
rails 2.3.8
cucumber 0.8.3
cucumber-rails 0.3.2
mongo 1.0.5
mongo_mapper 0.8.2
pickle 0.3.0
machinist 1.0.6
machinist_mongo 1.1.1
features/support/pickle.rb:
require 'pickle/world'
Pickle.configure do |config|
config.adapters = [:machinist]
end
I tried using config.adapters = [:machinist, Machinist::MongoMapperAdapter] but I get an error stating that there is no method factories for Machinist::MongoMapperAdapter.
undefined method `factories' for Machinist::MongoMapperAdapter:Class (NoMethodError) /usr/local/lib/ruby/gems/1.8/gems/pickle-0.3.0/lib/pickle/config.rb:25:in `factories'
features/support/machinist.rb:
require 'machinist'
require 'machinist/mongo_mapper'
require "#{Rails.root}/spec/blueprints"
require 'database_cleaner'
Before { Sham.reset } # reset Shams in between scenarios
spec/blueprints.rb (truncated for clarity)
require 'sham'
require 'faker'
Sham.code { Faker::Lorem.words 1 }
AccessCode.blueprint do
code
end
app/models/access_code.rb
class AccessCode
include MongoMapper::Document
key :code, String, :required => true
end

After days of beating my head against the wall, I have everything mostly working (I say mostly working because I'm not sure if there is something wrong that I haven't discovered yet). The fix was actually pretty simple once I figured it out.
To resolve the issue, and get my cucumber steps working with pickle, I changed MongoMapper::Document to include Pickle::Adapter::Base. I used lib/pickle/adapters/active_record.rb and data_mapper.rb (same path as active_record.rb) that come with pickle as an example. I did still need machinist_mongo, presumably to hook up pickle to my machinist blueprints.
I can't take credit for the code in def self.model_classes - it is stolen from tjtuom's pickle fork.
PS. If this is the completely wrong way to do it, please feel free to criticize or give suggestions, I am a complete ruby noob.
module MongoMapper::Document
module PickleAdapter
include Pickle::Adapter::Base
def self.model_classes
##model_classes ||= ::MongoMapper::Document.descendants.to_a +
::MongoMapper::Document.descendants.map { |klass| klass.subclasses }.flatten
end
# get a list of column names for a given class
def self.column_names(klass)
klass.column_names
end
# Get an instance by id of the model
def self.get_model(klass, id)
klass.find(id)
end
# Find the first instance matching conditions
def self.find_first_model(klass, conditions)
klass.find(:first, :conditions => conditions)
end
# Find all models matching conditions
def self.find_all_models(klass, conditions)
klass.find(:all, :conditions => conditions)
end
end
end
Set up pickle for machinist:
Pickle.configure do |config|
config.adapters = [:machinist]
end
To configure database_cleaner for mongo:
require 'database_cleaner'
require 'database_cleaner/cucumber'
DatabaseCleaner.orm = 'mongo_mapper'
DatabaseCleaner.strategy = :truncation

Related

What are the ways to override a frozen variable in ruby?

This is a selenium-webdriver commands.rb file where I want to edit the upload_file key of COMMANDS variable from [:post, 'session/:session_id/se/file'] to [:post, 'session/:session_id/file']. I want to extend this class to one of mine's and make this change permanent so that even if i bundle install it, this change shouldn't be gone.
module Selenium
module WebDriver
module Remote
module W3C
class Bridge
COMMANDS = {
upload_file: [:post, 'session/:session_id/se/file']
}.freeze
end
end
end
end
end
You can get around the issue of unfreezing by just assigning the constant to a new value:
Selenium::WebDriver::Remote::W3C::Bridge.const_set(:COMMANDS, {
upload_file: [:post, 'session/:session_id/file']
}.freeze)
You will get a warning, but it will work.
If you really want to unfreeze, I have to point you to another question on the topic: How to unfreeze an object in Ruby?
in response to comment
The easiest way is to use ActiveSupport Hash#deep_dup from ActiveSupport. If this is a non-rails project, you can add the activesupport gem and require 'active_support/all':
my_commands = Selenium::WebDriver::Remote::W3C::Bridge::COMMANDS.deep_dup
# Here we can change one key only, or do any other manipulation:
my_commands[:upload_file] = [:post, 'session/:session_id/file']
Selenium::WebDriver::Remote::W3C::Bridge.const_set(:COMMANDS, my_commands)
You can also do it without ActiveSupport, but you will need to be a little more careful about how you clone the object because deep_dup is not available, something like this would work instead:
my_commands = Selenium::WebDriver::Remote::W3C::Bridge::COMMANDS.clone.transform_values(&:clone)
And then run the same stuff as in the previous example.
To understand this, read up on the difference between a "shallow" vs "deep" copy of an Object in Ruby, or the difference between "clone" and "deep_dup". Also see Hash#transform_values which I used in that snippet, if you're not familiar with it.

Ruby-Rspec: Initialize PageObjects once in spec_helper instead of each spec

I variables set for xpaths in a file called PageObjects. Each spec I run I initialize the page objects with "p = PageObjects.new". However, I would like to initialize "p = PageObjects.new" once in "spec_helper.rb" instead of each spec.
This still gives me "error: uninitialized constant PageObject"...
require 'selenium-webdriver'
require 'yaml'
require 'rspec/retry'
require 'pry'
require 'bundler/setup'
p = PageObject.new
RSpec.configure do |config|
config.default_sleep_interval = 1
config.default_retry_count = 4
config.verbose_retry = false
config.display_try_failure_messages = true
config.exceptions_to_retry = [Net::ReadTimeout, Capybara::ElementNotFound]
end
Is there a way to achieve my goal by initializing PageObject once inside spec_helper rather than in each spec?
RSpec helpers seems to be the perfect solution for you
define the helper.rb
module Helpers
def p
#page_object ||= PageObject.new
end
end
Configure RSpec to include it:
RSpec.configure do |c|
c.include Helpers
end
And then you can use p method that will give you the PageObject:
specify do
expect(p).to be_a(PagObject)
expect(p.object_id).to eq(p.object_id)
end
You effectively want your test database to be maintained between tests. This is dangerous for a number of reasons, the most obvious being previous tests will affect future ones. As you're dealing with the same PageObject you will need to reset it between tests.
Putting that to one side, the options for enabling / disabling this can be found at:
https://relishapp.com/rspec/rspec-rails/docs/transactions, namely:
When you run rails generate rspec:install, the spec/rails_helper.rb
file includes the following configuration:
RSpec.configure do |config|
config.use_transactional_fixtures = true
end
The name of this setting is a bit misleading. What it really means
in Rails is "run every test method within a transaction." In the
context of rspec-rails, it means "run every example within a
transaction."
The idea is to start each example with a clean database, create
whatever data is necessary for that example, and then remove that data
by simply rolling back the transaction at the end of the example.
Disabling transactions If you prefer to manage the data yourself, or
using another tool like database_cleaner to do it for you, simply tell
RSpec to tell Rails not to manage transactions:
RSpec.configure do |config|
config.use_transactional_fixtures = false
end

Attempting to use pools crashes Celluloid

I'm trying to use pools in a project of mine that uses Celluloid. However, whenever I invoke the pool method on a class which includes Celluloid (thus receiving methods from Celluloid::ClassMethods) I consistently get the error:
NoMethodError: undefined method `services' for Celluloid:Module
router at /Users/my_username/.rvm/gems/jruby-9.0.5.0/gems/celluloid-supervision-0.20.6/lib/celluloid/supervision/deprecate/supervise.rb:54
supervise at /Users/my_username/.rvm/gems/jruby-9.0.5.0/gems/celluloid-supervision-0.20.6/lib/celluloid/supervision/deprecate/supervise.rb:6
pool at /Users/my_username/.rvm/gems/jruby-9.0.5.0/gems/celluloid-pool-0.20.5/lib/celluloid/supervision/container/behavior/pool.rb:13
<top> at celluloid_pool_test.rb:14
Specifically, this part seems to be the problem:
NoMethodError: undefined method `services' for Celluloid:Module
It tells me that the offending line is /Users/my_username/.rvm/gems/jruby-9.0.5.0/gems/celluloid-supervision-0.20.6/lib/celluloid/supervision/deprecate/supervise.rb:54. It turns out that line holds the code for the Celluloid::Supervision.router method:
def router(*_args)
# TODO: Actually route, based on :branch, if present; or else:
Celluloid.services ### this line is what causes the error
end
To make sure that the issue wasn't with my particular project, I grabbed a code sample from this article which utilizes pools and tried to run it:
require 'celluloid'
require 'mathn'
class PrimeWorker
include Celluloid
def prime(number)
if number.prime?
puts number
end
end
end
pool = PrimeWorker.pool
(2..1000).to_a.map do |i|
pool.prime! i
end
sleep 100
It failed with the exact same error as my project:
Finally, I ran a dead simple piece of code in IRB to see if pool is what triggers the error about services:
class Foo
include Celluloid
end
Foo.pool
Sure enough, I got the exact same error. It seems that there is a bug in Celluloid or that I'm not loading a dependency properly. However, I did require 'celluloid/supervision' in my attempts at solving this, to no avail. Am I doing something wrong on my end or is this a bug in Celluloid?
It seems that others have run into this issue before: https://github.com/celluloid/celluloid-pool/issues/10. I guess it has something to do with Celluloid.services being deprecated and not working in newer versions of Celluloid, so using require 'celluloid/current' rather than just require 'celluloid' seems to do the trick.

How to create a sandboxed RSpec environment?

Essentially, I want to create a program that will run some untrusted code that defines some method or class, and then run an untrusted rspec spec against it.
I've looked into sandboxing Ruby a bit, and this video from rubyconf was particularly helpful. After looking at several solutions, the two that appear to be the most helpful are rubycop, which essentially does static analysis on the code, and the jruby sandbox (both covered in above video). My instinct tells me that the jruby sandbox is probably safer, but I could well be wrong.
Here's a completely unsafe example of what I want to do:
code = <<-RUBY
class Person
def hey
"hey!"
end
end
RUBY
spec = <<-RUBY
describe Person do
let(:person) { Person.new }
it "says hey" do
person.hey.should == "hey!"
end
end
RUBY
# code and spec will be from user input (unsafe)
eval code
require 'rspec/autorun'
eval spec
Which all works fine, but the code obviously needs to be sandboxed. It will be a matter of minutes before some genius submits system("rm -rf /*"), fork while fork or something equally dangerous.
I made various attempts with the jruby sandbox...
sand = Sandbox::Safe.new
sand.eval("require 'rspec/autorun'")
sand.activate! # lock it down
sand.eval code
puts sand.eval spec
That code throws this exception:
Sandbox::SandboxException: NoMethodError: undefined method `require' for #<RSpec::Core::Configuration:0x7c3cfaab>
This is because RSpec tries to require some stuff after the sandbox has been locked down.
So, I tried to force RSpec to require stuff before the sandbox gets locked down by calling an empty describe:
sand = Sandbox::Safe.new
sand.eval("require 'rspec/autorun'")
sand.eval("describe("") { }")
sand.activate! # lock it down
sand.eval code
sand.eval spec
And I get this:
Sandbox::SandboxException: NameError: uninitialized constant RSpec
Which basically means that RSpec doesn't exist in the sandbox. Which is odd, considering sand.eval("require 'rspec/autorun'") returns true, and that the earlier example actually worked (RSpec's autoloader started to run).
It may be a problem with gems and this particular sandbox though. The sandbox object actually supports a method #require, which is essentially bound to Kernel.require, and therefore can't load gems.
It's starting to look like using this sandbox just might not really be possible with rspec. The main problem is trying to actually load it into the sandbox. I even tried something like this:
require 'rspec'
sand.ref(RSpec) # open access to local rspec
But it wasn't having any of it.
So, my question is two-fold:
Does anyone have any bright ideas on how to get this to work with the jruby sandbox?
If not, how secure is rubycop? Apparently codeschool use it, so it must be pretty well tested... it would be nice to be able to use ruby 1.9 instead of jruby as well.
It looks like the sand box environment isn't loading the bundle/gemset. RVM could be at fault here if you are using a gemset or something.
One might try loading the Bundle again once sand boxed.
I would look at ruby taint modes
$SAFE The security level
0 --> No checks are performed on externally supplied (tainted) data. (default)
1 --> Potentially dangerous operations using tainted data are forbidden.
2 --> Potentially dangerous operations on processes and files are forbidden.
3 --> All newly created objects are considered tainted.
4 --> Modification of global data is forbidden.
I have been trying to figure out a similar problem. I want to use some gems like json and rest-client inside my sandbox after activating it. I tried following.
require "sandbox"
s=Sandbox.safe
s.eval <<-RUBY
require 'bundler'
Bundler.require :sandbox
RUBY
s.activate!
Gemfile.rb
group :sandbox do
platforms :jruby do
gem 'json'
gem 'rest-client'
end
end
This way, I was able to require gems in my sandbox. But, then there were some gem specific issues with sandbox. For eg, I had to add a method initialize_dup to whitelist for safe.rb in jruby-sandbox. RestClient has some problem with Fake File Sytem ALT_SEPARATOR which I am trying to patch. You can try this approach for RSpec and see if everything goes through.

Requiring gem in Rails 3 Controller failing with "Constant Missing"

I've seen this asked a few times in other threads, but none of the answers seem to apply.
Environment:
Rails 3
amazon/ecs gem from jugend. The lone file is here:
http://github.com/jugend/amazon-ecs/blob/master/lib/amazon/ecs.rb
my gemfile has:
gem 'amazon-ecs', :git => 'git://github.com/jugend/amazon-ecs.git'
Everything works in irb. I can run:
bundle console
require 'amazon/ecs' and then go to town
when I try to use it from the controller though, like so:
require 'amazon/ecs'
require 'amazon/ecs'
class SearchController < ApplicationController
def index
end
def results
Amazon::Ecs.configure do |options|
options[:aWS_access_key_id] = '[key]'
options[:aWS_secret_key] = '[secret]'
end
res = Amazon::Ecs.item_search(params[:search], {:response_group => 'Medium', :search_index => 'All'})
end
end
I get: uninitialized constant SearchController::Amazon at line 8, where I first try to use Amazon.
the ecs.rb has a module Amazon containing a class Ecs. I'm not sure why this is working in erb, and not in rails.
I'm still kinda new to Rails, so please answer using small words. :-/
Was given the answer. I moved my initialization code to an initializer in config/initializers file, removed the require entirely, and things worked. I don't know why though, so if someone could answer that, that'd be great.
All of the gems require their files by default, so usually you don't need to explicitly require any files.
Speaking about your problem, it could somehow be, that your controller is run before Amazon module is processed.

Resources