Rails, Cucumber, Capybara, Selenium, Pow, domains, subdomains and javascript - ruby-on-rails

TL:DR - How can I make Cucumber request a page through the app but pretend that the request came from "http://mysubdomain.mydomain.dev", and run the javascript properly?
Ok, this may take a while to explain, but please bear with me...
I'm a 'relatively' experienced Rails dev, but only recently took the plunge into full BDD/TDD.
The page I'm testing in cucumber has some javascript in it for creating a new nested object (very similar to this RailsCast).
Now the problem lies in the fact that not only do I have variable sub-domains, I have variable Domains too (Just trust me on that one...)
So the app needs to query the request.host to find the current_domain and the current_subdomain before everything ties up in the background and it can carry on serving the right bits of the app.
I've managed to get all of the tests passing fine using the host! domain and Capybara.default_host = domain techniques in the background steps.
However, when I use the #javascript tag on the feature that tests a page with .js on it, I get Firefox grabbing focus, then trying to load the full url.
Now it also happens that I'm running Pow, and have those urls hooked up to the dev server. Unsurprisingly, they don't work as when the user tries to log in, it's looking in the dev DB. I tried running the suite after turning the pow server off, and it just timed out.
Surely the javascript webdriver shouldn't be actually accessing the url, but simply running the app itself and pretending that the host is what I tell it to be???
I'm obviously missing something here - How can I make Cucumber build the page internally, but pretend that the request came from "http://mysubdomain.mydomain.dev"?
edit: Jason - the variable domain trick is achieved exactly the same as subdomains... If you can query the db for an account based on request.subdomains.first, you can query for a domain through request.domain. You just have to double check a few things like capitalisation etc. to minimize the risk of a malformed url breaking stuff, and obviously you need to ensure that the domain record exists in the db first...
Oh - and cache the domain record requests with care...
It means that you can serve the same app, but with different styling and landing pages etc. Useful for PaaS apps with a broad customer base - you can re-brand it and sell it as a specific solution to one group's problems, even though it's the same guts underneath.

I am looking to do something very similar. I thought about adding an additional pow directory for test, then using the pow directives to override the environment. I think this is done within a ".powenv" inside your app directory. Here is an example of a quick fix:
echo export RAILS_ENV=cucumber > .powenv && touch tmp/restart.txt
But it would be better to do something dynamic so that before you start the test, you can tell pow what env to run, then when finished switch back, maybe even run the test server on a different port on a temp basis. Pow is the only great solution I know of so far for handeling subdomains.
EDITED: I have this working now in my environment with the following addition to my features/support/env.rb file.
# Switch Pow to For Cucumber Tests
Capybara.default_driver = :selenium # Subdomain testing will only work with pow and selenium
pow_config = "#{Rails.root}/.powenv" # Dont change, this is the Config Files Location.
pow_config_stash = "#{Rails.root}/.powenv_original" # This is what the config will be stashed as during testing.
Before do
# Set the default host
Capybara.app_host = "http://www.resipsa.dev"
# Stash the existing config
File.rename(pow_config,pow_config_stash) if File.exists? pow_config
# Write the new pow config
f = File.new("#{Rails.root}/.powenv", "w")
f.write "export RAILS_ENV=test"
f.close
# Touch tmp/restart.txt to force a restart
FileUtils.touch "#{Rails.root}/tmp/restart.txt"
end
After do
# Delete the temp config
File.delete(pow_config)
# Restore the Original Config
File.rename(pow_config_stash,pow_config) if File.exists? pow_config_stash
# Touch tmp/restart.txt to force a restart
FileUtils.touch "#{Rails.root}/tmp/restart.txt"
end

Related

Rails adding translations to a running rails environment

Is there a way to manually inject a translation through the Rails console ?
Suppose I am working on a dev environment, and I want to test some code in a production console (eg to test some statistics on real data).Problem is, the code I want to test relies on new translations that didn't exist (or were changed) in the production environment.
So my code returns a lot of translation_missing
Can I inject the missing translations ? Via a hash or a YML file ?
I18n.load_translations(hash_or_file)
Usually Application instances that serve http requests (for example running under Unicorn/Puma) are not available via Rails console. When someone login to production server and type $RAILS_ENV=production rails c it starts another application process. Translations dictionary is a kind of in-memory cache and usually it is not possible to change that cache for/from another process (in general). You can reload translations only for application instance that started by Rails console, but not for running server.
Only one way to hot reload translations is adding kind of a hook into source code of application to re-read YAML file, but it seems better just restart application server.
UPDATE: For testing purposes I18n cache could be modified like:
I18n.backend.send(:translations)[:en][:date][:formats][:default] = "%Y-%Z"

Rspec: run an outside rails server

This question is about starting a rails server of the external project from a rspec environment.
There is 2 projects.
First project act as the Admin Back Office, it's the central application where users interact with web pages. I call it BackOffice
Second project is a Json API Server which will receive commands from the Admin Back Office through json requests.I call it ApiServer
I am trying to test API interaction between those 2 rails projects, and I would like to set-up rspec so I can write and maintain my spec files in BackOffice project. Those specs would start a ApiServer rails server and then play around to perform the tests.
My issue is about starting the ApiServer rails server. After looking at the rails app initialization files, I assumed I had to add a require to "config/environment".
But when I insert into BackOffice/spec/spec_helper.rb
require File.expand_path('../../../ApiServer/config/environment', __FILE__)
I get the error
`initialize!': Application has been already initialized. (RuntimeError)
# Backtrace to the file:
# ApiServer/config/environment.rb
# Line:
# Rails.application.initialize!
I also tried to simply call the following in backticks
`cd /api/path; bundle exec rails s -p 3002`
but got the same kind of error
Then I got inspiration from Capybara source code, and required the "ApiServer/application", then I am able to create a ApiServer.new object, but as soon as I call initialize! on it it I get the same message.
Any help is greatly appreciated. Cheers
Actually the second app is nothing more then an external service, which is better to stub for the tests.
There is one nice article from thoughtbot about using vcr gem to mock external web services:
https://robots.thoughtbot.com/how-to-stub-external-services-in-tests
Obligatory "don't do that unless you really need to".
However, since it seems you know what you need:
Short answer:
You need to isolate both application in system environment and launch it from there using system-calls syntax.
Long answer:
What you're trying to do is to run two Rails applications in the same environment. Since they both are Rails applications they share a lot of common names. Running them ends in name clash, which you're experiencing. Your hunch to try simple back ticks was good one, unfortunately you went with a bundler in already existing environment, which also clashes.
What you have to do in order to make it work is to properly isolate (in terms of code, not in terms of network i.e. communication layer ) application and then run launcher from rspec. There are multiple ways, you could:
Use Ruby process control (Check this graph, you could try to combine it with system level exec)
Daemonize from Operating System level (init.d etc.)
Encapsulate in VM or one of the wrappers (Virtualbox, Vagrant, etc.)
Go crazy and put code on separate machine and control it remotely (Puppet, Ansible, etc.)
Once there, you can simply run launcher (e.g. daemon init script or spawn new process in isolated environment) from RSpec and that's it.
Choosing which way to go with is highly dependent on your environment.
Do you run OSX, Linux, Windows? Are you using Docker? Do you manage Ruby libraries through things like RVM? Things like this.
Generally it's a bad idea to require launching another service/application to get your unit tests to pass. This type of interaction is usually tested by mocking or vcring responses, or by creating environment tests that run against deployed servers. Launching another server is outside the scope of rspec and generally, as you've discovered, will cause a lot of headaches to setup and maintain.
However, if you're going to have these rails projects tightly coupled and you want them to share resources, I'd suggest investigating Rails Engines. To do this will require a substantial amount of work but the benefits can be quite high as the code will share a repository and have access to each other's capabilities, while maintaining application isolation.
Engines effectively create a rails application within another rails application. Each application has it's own namespace and a few isolating guards in place to prevent cross app contamination. If you have many engines it becomes ideal to have a shell rails application with minimal capabilities serving each engine on a different route/namespace.
First you need to create housing for the new api engine.
$ rails plugin new apiserver --mountable
This will provide you with lib/apiserver/engine.rb as well as all the other scaffolding you'll need to run your API as an engine. You'll also notice that config/routes.rb now has a route for your engine. You can copy your existing routes into this to provide a route path for your engine. All of your existing models will need to be moved into the namespace and you'll need to migrate any associated tables to the new naming convention. You'll also have some custom changes depending on your application and what you need to copy over to the engine, however the rails guide walks your through these changes (I won't enumerate all of them here).
It took a coworker about a week of work to get a complicated engine copied into another complicated rails server while development on both apps was occurring and with preserving version control history. A simpler app -- like an api only service -- I imagine would be quicker to establish.
What this gives you is another namespace scope at the application root. You can change this configuration around as you add more engines and shared code to match various other directory structures that make more sense.
app
models
...
apiserver
app
...
And once you've moved your code into the engine, you can test against your engine routers:
require "rails_helper"
describe APIServer::UsersController do
routes { APIServer::Engine.routes }
it "routes to the list of all users" do
expect(:get => users_path).
to route_to(:controller => "apiserver/users", :action => "index")
end
end
You should be able to mix and match routes from both services and get cross-application testing done without launching a separate Rails app and without requiring an integration environment for your specs to pass.
Task rabbit has a great blog on how to enginize a rails application as a reference. They dive into the what to-do's and what not-to-do's in enginizing and go into more depth than can be easily transcribed to a SO post. I'd suggest following their procedure for engine decision making, though it's certainly not required to successfully enginize your api server.
You can stub requests like:
stub_request(:get, %r{^#{ENV.fetch("BASE_URL")}/assets/email-.+\.css$})

Do something after Heroku deployment

Is there a hook or event that can be used in order to trigger something immediately after a Rails app is deployed to Heroku?
Specifically, we want to build out a certain directory structure under tmp/ (the structure is contingent on config values so it can't be checked into source).
Heroku supports a few after deploy hooks - see https://devcenter.heroku.com/articles/deploy-hooks .
You can use the HTTP hook to trigger an action on your app which will create your desired structure. As an example of how to use this HTTP deploy hook, check out http://dzello.com/blog/2012/03/11/spin-up-your-heroku-dyno-automatically-after-deploy/
Keep in mind though that if you build anything in the tmp directory, it'll go away after a dyno restart.
Turns out the two parts of my question are not compatible- #eugen explains how to implement a post-deploy hook, but also reminded me that any tmp directories I create will be blown out whenever a dyno restarts...
So what's the solution? The following is what I came up with:
config/initializers/setup.rb
# Ensure temp file system is set up:
begin
Dir.mkdir("tmp/customdir") unless File.exists?("tmp/customdir")
rescue StandardError => e
# Allow this to fail on initial deploy
# (It will raise error: "No such file or directory - tmp/customdir" because the tmp dir won't yet exist)
end
Explanation:
Heroku essentially does a two-pass initialization during deploys. The first pass, when it pre-compiles assets, would normally choke on the above code because the tmp directory will not yet exist. If we allow it to fail, it will work on the second pass, and on subsequent restarts.
Yeah it's a hack. I've tried before to figure out how to distinguish between a pre-compile initialization and normal initializations, but there doesn't seem to be a way, so this is what we're left with.
If anyone can improve on this would love to hear it.

confusing about autoload_paths vs eager_load_paths in rails 4

I read a post about the rails load_paths, here is the link.
But, I am still confused about the difference between the autoload_paths and eager_load_paths:
I have tested them in a newly created Rails 4 project. It seems that they run the same way, that auto-reload in the development mode but in the production mode.
Author of the linked article here. Here's an attempt to clear up the confusion, going off of #fkreusch's answer.
In Ruby you have to require every .rb file in order to have its code run. However, notice how in Rails you never specifically require any of your models, controllers, or other files in the app/ dir. Why is that? That's because in Rails app/* is in autoload_paths. This means that when you run your rails app in development (for example via rails console) — none of the models and controllers are actually required by ruby yet. Rails uses special magical feature of ruby to actually wait until the code mentions a constant, say Book, and only then it would run require 'book' which it finds in one of the autoload_paths. This gives you faster console and server startup in development, because nothing gets required when you start it, only when code actually needs it.
Now, this behavior is good for local development, but what about production? Imagine that in production your server does the same type of magical constant loading (autoloading). It's not the end of the world really, you start your server in production, and people start browsing your pages slightly slower, because some of the files will need to be autoloaded. Yes, it's slower for those few initial requests, while the server "warms up", but it's not that bad. Except, that's not the end of the story.
If you are running on ruby 1.9.x (if I recall correctly), then auto-requiring files like that is not thread safe. So if you are using a server like puma, you will run into problems. Even if you aren't using a multi-threaded server, you are still probably better off having your whole application get required "proactively", on startup. This means that in production, you want every model, every controller, etc all fully required as you start your app, and you don't mind the longer startup time. This is called eager loading. All ruby files get eagerly loaded, get it? But how can you do that, if your rails app doesn't have a single require statement? That's where eager_load_paths come in. Whatever you put in them, all the files in all the directories underneath those paths will be required at startup in production. Hope this clears it up.
It's important to note that eager_load_paths are not active in development environment, so whatever you put in them will not be eagerly required immediately in development, only in production.
It's also important to note that just putting something into autoload_paths will not make it eager-loaded in production. Unfortunately. You have to explicitly put it into eager_load_paths as well.
Another interesting quirk is that in every rails app, all directories under app/ are automatically in both autoload_paths and eager_load_paths, meaning that adding a directory there requires no further actions.
Basically, autoload_paths are paths Rails will use to try loading your classes automatically. E.g. when you call Book, if that class isn't loaded yet, it will go through the autoload_paths and look for it in those paths.
In production, it might be better to load those upfront to avoid autoload concurrent issues. For that, it provides the eager_load_paths. Paths in that list will be required upfront when your application starts.

Can I change config.cache_classes programatically in Rails 3?

I have some iPhone client tests that run against my development rails server. The whole suite runs an order of magnitude faster if I turn on class caching in the Rails config. On the other hand, that slows down development when I'm not actually running the tests.
I want the test suite to hit an action at the beginning to turn on class caching and another action at the end to turn class caching off again.
Is this even possible? If so, how?
Not without some serious hacking. Rails goes to quite a lot of trouble to make sure your files are reloaded on every request (when cache_classes=false). The value of the cache_classes configuration variable is used by initializers in several places not the least of which being:
using require to load ruby files when cache_classes is true (meaning they are no longer reloadable)
setting up dispatcher callbacks to reaload the application on every request when cache_classes is false
You do have access to the value of the cache_classes variable, and you can even change it if you like:
Rails.configuration.cache_classes = true
But, this will have no effect on the running rails instance as the initializers where that value is used only run once when the rails app starts up.
What this means is this, unless you're prepared to invest some serious time and hacking effort you can't really avoid a restart of your server. So, what you need to look at is controlling this restart process via your test suite.
For example you can try to restart rails from within rails. This would allow you to define an action that your test suite can hit right before it begins executing (to restart the server in the right mode), and another action which the server can hit after all tests have finished, to restart everything with cache_classes set to what it used to be. You would control the value of cache classes via an environment variable like this post suggests.
It would still require a bit of work to set all of this up and get it to hang together, but this is probably your best bet if you want an 'auto-magical' solution.
I don't think doing what you suggest will work.
But I suggest you may be looking for the wrong solution.
If what you want is to access your development database from your iphone testing,
then why not add a new environment.
Add a new file config/environments/iphone_dev.rb
require File.dirname(__FILE__)+"/development.rb"
config.cache_classes = true
And in your database.yml (or mongoid.yml or whatever)
iphone_dev:
host: localhost
database: my_app_development
There is no reason the database cant be the same
Now just run rails server -eiphone_dev -p3001
You should have a server, almost the same as your dev server,
but running on a different port, with caching enabled.

Resources