With Rails ‘respond_to’ method, it is really easy to return differnt
kinds of data from the same controller method based on the url
extension. I was really happy with this when I was building a a site
to provide a read-only version of an event calendar I had built years
earlier on the ArsDigita Community System. To get data out of that
Oracle database, I built a Rails web site that let users ask for
events by category, sponsor, or lecture series with a variety of date
restrictions. The data was consumed by several different clients - two
who wanted XML and several others who wanted JSON. I could use exactly
the same URLs by just adding .xml or .json to the url. Even better, I
could have controller render an html version of the same data which
developers could use to preview the result sets. Once they had all the
parameters the way they wanted, they just copied the url and added
‘.json’ ahd used it in their web pages.
But now I actually want to move into the 21st century and build some
web pages with AJAX interactions. Rails has supported a variety of
methods of working with Javascript starting with the RJS methods in
the version 1.x days. Currently Rails (4.2.0) uses JQuery by default -
including a jquery-ujs package which, when combined with the ‘remote:
true’ argument to ‘form_for’, makes it simple to have your forms
submit via AJAX instead of a normal server request.
First, the regular form_for helper:
# This rails helper<%= form_for(@course) do |f| %>
# Produces this html
<form class="new_course"id="new_course"action="/courses"accept-charset="UTF-8"method="post"><inputname="utf8"type="hidden"value="✓"><inputtype="hidden"name="authenticity_token"value="YE006c08mOnIYENufqJJZVrV3yAo1sHOgk9EJ9ZqigLIUktT6Vg0Px8FMx5GTl54ewIKYOaWqJYpWgiPEpQDZQ==">
But with ‘remote: true’ (note no authenticity_token):
# This rails helper<%= form_for(@course, remote: true) do |f| %>
# Produces this html
<form class="new_course"id="new_course"action="/courses"accept-charset="UTF-8"data-remote="true"method="post"><inputname="utf8"type="hidden"value="✓">
This comes into the server and is interpretted as a JS request:
StartedPOST"/courses"for<ip>at2015-03-1223:07:10-0700ProcessingbyEmpcms4Tutors::CoursesController#create as JS
which will fall into the format.js case in the normal respond_to
stanza, which by default, will try to render the create.js.erb
template in your courses views. This view should jquery directives
that will be executed in the browser.
This flow has some pros and cons. On the bright side, it is relatively
easy (as long as you remember to use ‘escape_javascript’ on any html
you produce. But it means you have a fair amount of javascript
scattered small files in your views directory. It is JS that
manipulates your html so in that sense it is a view. But even if I am
less than enthusiastic about that aspect, the big selling point is
that I can reuse the code in my view partials. The exact same embedded
ruby (erb) that created the table rows that loaded when the page first
loaded is the erb that creates the new row JQuery inserts when I
finish adding a new course.
Speaking JSON
If you want to keep all of your JavaScript together, for example if
you are using a more extensive front end framework, then you may want
to have your AJAX interactions communicate as JSON. The adjustments
you need to make for that start with adding a data-type argument to
your form tag:
# This rails helper<%= form_for(@course, remote: true, format: json) do |f| %>
# Produces this html
<form class="new_course"id="new_course"data-type="json"action="/courses"accept-charset="UTF-8"data-remote="true"method="post"><inputname="utf8"type="hidden"value="✓"># Which shows up as this on the serverProcessingbyEmpcms4Tutors::CoursesController#create as JSON
If you don’t add a different format section, this will also end up in
the format.js section and will send back the jquery selectors and
manipulations as before - with a Content-Type header of
‘text/javascript’. However, since the browser wasn’t expecting to get
JavaScript, it doesn’t evaluate it (at least Chrome doesn’t). So I can
see the server responding without error, and the data coming into the
browser with a status code of 200. No JavaScript errors register in
the console - but nothing happens on the page. All in all my least
favorite type of error - no error, just nothing happens.
So to correct that, we add a ‘format.json’ section to our ‘respond_to’
block:
format.json{renderjson: @course}
So now the browser gets back a JSON representation of the new item
with a Content-Type header of ‘application/json’. Then it is up to us
to write the client-side JavaScript - for example to add the table row
with the new element. That might look something like this (untested code):
$('#all_courses).on('ajax:success', function(event, data, status, xhr) {
// add the table row with the course name, links, etc.
}.on('ajax:error', function(event, xhr, status,error) {
// insert the failure message near the form
$('form').append(xhr.responseText)});
This keeps all your JavaScript on the client side - but at the expense
of mixing some html into it as you build the new table row.
We just got new Macs at work - which came with Yosemite
preinstalled. There are a few odds and ends that I am not thrilled
with, but overall I have found the transition from Lion to be quite
smooth. So guess it is time to upgrade my personal laptop - before
any more security issues are announced.
I went to the App Store and downloaded the installer and ran it. All
seemed to be going well until the point where the installer thought it
had “2 minutes left”. Two hours later, I gave up and went to bed,
leaving it running. I have no idea when it actually finished but in
the morning, my machine was asleep. When I woke it up, I got the odd
gray screen overlay, with the blinking gray startup bars near the
bottom - like you get when you have run the battery completely out. I
was wondering if either the install had failed (probably not, since
the screen behind the gray overlay was the new, flat, Yosemite look)
or if the machine had overheated and crashed. It got very hot during
the install but I had not heard the fan running. I did what I could to
leave the bottom of the machine with good airflow so it could cool off
but that was all I could do for that issue.
Anyway, despite the odd gray screen, the machine started up OK, let me
log in, and then ran some “setting up your new mac” things. Looks
OK. I launched the app store and installed the two security updates
that have come out since Yosemite launched. I also downloaded the Mac
office apps, pages, keynote, numbers….
XCode
I downloaded the newest XCode (6.1) and installed it. The first time
it launched, I was asked to allow it to upgrade the device support -
which it did and then promptly crashed. When I tried to launch it
again, it crashed again. But third time is the charm. It again asked
if it could run some (different) updates, but this time, it then gave
me the normal XCcode interface. Until I get around to learning iOS
programming, I don’t really have much use for the XCode GUI - except
for using the iOS simulator as one of the targets for Karma to run
JavaScript tests in a browser. The reason I need XCode is for the
compiler. To get the command line tools installed, I ran xcode-select
--install and agreed to install the command line tools. Then I ran
xcodebuild --license. This was supposedly to accept the Xcode
license - but just like when I did it for setting up my work machine,
I got xcodebuild: error: invalid option '--license'. That Chris said
that indicated that I had already accepted the license; not sure about
any of that, but it doesn’t seem to have interfered with compiling
stuff at work, so I am not going to worry about it.
X11
X11 no longer comes with OsX, but when I tried to use the X11 icon in
my dock, it helpfully redirected me to this page
which allowed my to download XQuartz 2.7.7
The installer ran just fine and at the end told me I had to log out
and back in again to make this the default X11 for my machine.
Homebrew
I should have done a little checking and preparation before I did my
upgrade. At least with Mac Ports, you generally can’t run any of the
port commands after you have upgraded the OS. This means that it is
really hard to figure out which packages you had installed (in
particular, which were installed explicitly and which were installed
because they were dependencies for something else you asked for)
unless you ask for a list before doing the upgrade. While I was
looking for blog posts about updating my homebrew-installed software,
I found this blog post
which explains why my Yosemite upgrade took FOREVER once it got to the
2 minutes left stages. Something about moving /usr/local and then
moving all the files back… one by one. Oh well, at least now I know
what that was about.
Fortunately, it looks like Homebrew will still basically work after
the upgrade. I can run brew list and get a reasonable list of
installed packages: emacs, docbook, graphviz, imagemagick, mongodb,
mysql, node, postgresql, python, wget, etc. The blog post above
recommended running brew doctor which gave me several sets of
advice:
some broken symlinks to remove with brew prune
warnings about unlinked kegs
a keg-only formula for libxml2 that is linked into /usr/local
a warning that I should run brew update to get the latest formula info
The last one seemed reasonable, so I started there. The output of that
command (and some reading of docs and the install script) explained
why brew survived my upgrade. Homebrew just uses the system ruby -
which is always there and should always run on the installed OS. And
it explains why I don’t see separate ‘upgrade homebrew itself’ and
‘update the package list’ commands. Both are taken care of by running
brew update which checks out the latest software and package
information from Github:
I think I want to update just about everything. But I am a little
concerned about upgrading the databases. Let’s start by pinning them,
then upgrading everything else, and then upgrading them one at a
time. But first, do they run now?
Looks like mysql is running - I see processes for mysqld_safe and
mysqld in ps and I can log in as root and poke around. The postgresql
server is not running at the moment and I mostly have not been using
it. So I am just going to pin those two packages and upgrade
everything else:
Then the updates all ran - some giving messages, mostly about how to
launch daemons or warning you not to link certain libraries because
they would conflict with the OsX versions. To pick up the last few
upgrades, I did as suggested and ran brew link fontconfig pixman and
then reran brew upgrade. I will save the output of all of these
upgrades in case I need to go back to it.
MySQL
The MySQL upgrade is only one minor version different, so I think I’ll
try to do that upgrade. About the only database I care about in the
current one might be the cardsharp_dev database, so I am not too
worried about the data even if there is some strange incompatibility.
Logged in and looked around. Seems fine at least at a glance.
Starting and stoping services
While I was looking around for how to stop MySQL before the upgrade, I
found two useful resources, this launchctl tutorial
and this blog post on a brew command to replace all this: brew services
Perpahs I should leave it but I would really like to have everything
upgraded for the start of the new year. I had trouble getting to old
databases after a previous postgres upgrade. So first I want to take a
backup. And to do that, I need to start the database server…..
It looks like the [Yosemite upgrade removed a few empty directories]
(http://stackoverflow.com/questions/25970132/pg-tblspc-missing-after-installation-of-os-x-yosemite)
that we need:
I logged in and looked around. Looks like there are 2 tablespaces (\db)
both owned by me - and a single schema (\dn) - also owned by me. What
I missed, was how to list the databases: \l. So I missed doing the
pg_dump before I upgraded the database. Fortunately there is an
upgrade command which I found detailed at
https://kkob.us/2014/12/20/homebrew-and-postgresql-9-4/.
There was a lot more output - and eventually the upgrade failed
because I had already created the database ‘cnk’. So let’s remove the
new stuff and try again:
The end of the initdb output mentioned it was enabling ‘trust
authentication’ for all local connections. I only want my user to be
able to log into postgres. So before starting up the database, I
edited /usr/local/var/postgres/pg_hba.conf to change the user from
‘all’ to ‘cnk’.
After a lot of interruptions for other work, last week I finally
finished recreating my CMS app in Rails 4 - just in time for the
release of Rails 4.2! Most of the information I have been reading
about 4.2 has focused on new features, adequate record, foreign keys,
active job, etc. So I was not really prepared for a number of issues
that came up as a result of changes and depreciations. On my first
pass, 871 of my 1419 tests failed! So guess I had better take some
notes as I go along.
To get started, I changed the Rails version in my Gemfile from 4.1.8
to 4.2.0 and ran bundle upgrade rails and then ran my rspec test
suite (rspec-rails 3.1.0 with rspec-core 3.1.2). As I said, a TON of
tests failed, including the vast majority of my controller tests.
SSL Requirement
All (or nearly all) of the controller test failures pointed to my
SSL-requirement gem. I need to go back and diagnose that - but since I
can’t easily actually run SSL on my dev server right now, I commented
out the gem in my Gemfile and include lines in my application
controller:
diff--gita/Gemfileb/Gemfile-gem'bartt-ssl_requirement',:require=>'ssl_requirement'+# gem 'bartt-ssl_requirement', :require => 'ssl_requirement'diff--gita/app/controllers/application_controller.rbb/app/controllers/application_controller.rb-include::SslRequirement-ssl_allowed:all+# include ::SslRequirement+# ssl_allowed :all
Deprecation Warnings
New Configuration Options
Each time I run my tests, I was getting a couple of deprecation
warnings, one about renaming a configuration option
serve_static_assets to serve_static_files and one about suppressing
errors raised within after_rollback and after_commit callbacks. The
first one was fairly easy to deal with. I just searched my config
directory and changed the option name in my test.rb and production.rb
files. It isn’t set in development.rb so I continued to leave it out.
For the second warning, I tried adding an option in each of my 3
environments files - setting the option to true for dev and test but
keeping the old exception suppression by setting this to false in my
production.rb file.
# Don't suppress errors in after_rollback and after_commit callbacksconfig.active_record.raise_in_transactional_callbacks=true
Unfortunately, setting those options did not fully suppress the
deprecation warnings. I am still getting them for all 4 of the models
I have that use paperclip (my documents model and all 3 of my image
type models). Before reporting an issue, I thought I would try
upgrading to the latest version of paperclip. Running bundle update
paperclip upgraded me from paperclip 4.2.0 to 4.2.1 and cocaine 0.5.4
to 0.5.5. And that did remove the remaining
‘raise_in_transactional_callbacks’ warnings when I run my tests!!!
Truthiness
One of my tests had several instances of the following warning:
All of these were coming from sections of the test where I was doing a
create or update with invalid parameters. I had not given much thought
to those params but assigned a nonsense value to one of my boolean
columns:
The model in question is not super picky, so it is a little difficult
to find parameters that would invalidate the model. For my create
actions, sending in an empty file attribute would invalidate the model
object. But for an update, an empty file would be perfectly legitimate
since one can update other attributes without uploading a new
file. But, since I am mocking out the save action to return false in
my ‘update with invalid params’ tests, it is good enough.
Page Versions
When running the pages controller spec, I got a bunch of warnings
about serialized_attributes. I suspect those are coming from my
versioning system, paper_trail. But I noticed that there is also a new
version of the tree plugin I use, closure tree. Looking at the
changelog, I didn’t see anything that looked like it would make any
difference but decided to upgrade anyway so as to keep up; this took
me from closure_tree 5.0.0 to 5.2.0.
Upgrading paper_trail from 3.0.5 to 3.0.6 did not make any difference
in the serialized_attributes warnings:
Looking at the issue queue, I guess I should have known about this for
quite a while: https://github.com/airblade/paper_trail/issues/416
There doesn’t seem to be any consensus about a solution. For now I
think I’ll ignore it - even though it means my page controller and
versions controller specs are kind of noisy.
Errors
Changes to assert_select
Almost all of my request specs and a bunch of my view specs are
failing with messages like:
Fiddling with a few gem updates didn’t seem to be making any
difference to this, so I decided to move my Gemfile.lock to
Gemfile.lock.bak (for reference) and let bundler install the latest
compatible versions of things. This made the following updates:
I also commented out coffee-rails because I am not using coffeescript;
this removed the coffee-rails, coffee-script-source, and subexec gems.
This upgrade took care of most of the errors I was seeing in my view
specs. The few remaining problems pointed out actual errors - mostly
some form tags that had been copied over from the rails 2.3 version of
this project. I also had an error in the user mailer - again due to
misuse of url_for to get an https url. I fixed those and now all of my
model, mailer, routing, controller, helper, and view tests pass. So
now it is just my request specs that are dorked:
All the remaining errors still refer to document_root_element not
being implemented. Grepping through the gems in my Rails 4.2 gemset,
most references come from a new gem, rails-dom-testing. The
README
for the gem shows an example of using document_root_element - and
refers us to the API docs in selector_assertions.rb.
But even after reading that, I am somewhat unclear about what to do.
More poking around - by searching on github - turned up 2 pull
requests: https://github.com/rails/rails/pull/17107 and
https://github.com/rails/rails-dom-testing/pull/20 Again, very
confusing but suggesting the need to add an alias to html_document -
temporarily?
I looked at the other places where I found document_root_element in my
rails 4.2 gemset. All 3 were places where a method by that name was
defined, one each in
actionpack-4.2.0/lib/action_controller/test_case.rb,
actionpack-4.2.0/lib/action_dispatch/testing/integration.rb, and
actionview-4.2.0/lib/action_view/test_case.rb. Two out of 3 of those
defined it as:
defdocument_root_elementhtml_document.rootend
So I tried dropping that into one of my test files and that caused
the tests in that file to pass.
To include this in all my request specs, I created a module and
included it only in my request specs:
With that addition, almost all of my request specs passed. I had a
couple of places (in request specs and in my view specs) where I
needed to change my html entities to the characters they evaluate to -
as warned about in the release notes:
http://edgeguides.rubyonrails.org/4_2_release_notes.html#assert-select
After taking care of those issues, all my tests pass again!
Other
I still haven’t figured out the SSL requirement errors. But that is
for another post.
After that, I may still want to look into rest of
the release notes
to see if there is anything else I should examine:
And I should read through the
upgrade guide
to see what else I might want to deal with.
There is also an intro to Rails 4.2 video
though I suspect that will be about the new features and not about
some of the refactoring or internal changes.
OK after a Pythonic interuption and a great vacation, back to my Rails
upgrade. The original project was built way before the introduction of
the asset pipeline. We we did the last rebranding, I was going
cross-eyed trying to find all the references to colors - esp. as there
were a couple of itterations from the design firm about exactly which
gray was what. So I made myself a global color and font variable list
and started to use less in development - with Less.app to recompile
the stylesheets on the fly. I could just continue doing that - but it
seems a shame not to use one of the big improvements in the Rails
ecosystem. And we could use some help to get better browser/proxy
caching and reliable cache expiration upon update.
So, I copied my *.less files from ‘public/stylesheets/’ into
‘app/assets/stylesheets’, renamed them from ‘.less’ to ‘.css.scss’,
and updated all the variable references from @thing to $thing. Since I
want my global variables used everywhere, I want a global compilation
scope. According to the ROR Guide to the asset pipeline,
that means I should use SASS’s @import directives rather than the
rails asset pipeline require mechanism. I thought I understood - but
then I was just getting an application.css file with the naked
‘@import “whatever”’ lines. The part I had overlooked was changing
‘application.css’ to ‘application.sass’. Once I did that, I got my
content - with the variables correctly interpolated. I was still
missing a couple of images referenced in the stylesheets. To fix that,
all I needed to do was change from hand coding the url attribute
background: url('/images/mte/search_button.png') no-repeat; to using
the image-url helper background: image-url("mte/search_button.png")
no-repeat;.
Getting my fonts to show up was a little more confusing. I started
with a blog post from Atomic Object.
It was for Rails 3.1 but I don’t think there have been major changes
to the asset pipeline for Rails 4.1 - and the css files they generated
from Font Squirrel look a lot like the files we got from the design
firm. In the current site, I have all the font information in
/public/fonts. I initially tried moving that directory into
/app/assets/ but I had trouble finding an import statement that
got the SASS compiler to include them in the compiled application.css
file; using a relative url @import "../fonts/Caslon" got SASS to
include them. But then I had trouble with the font-url helper -
couldn’t figure out how to specify the location of the actual font
files. The easiest working combination seems to be to move the fonts
directory to app/assets/stylesheets/fonts, import Vaud.css.scss
using @import "fonts/Vaud", and then use font-urls like
src: font-url('fonts/VaudBook-webfont.eot'); /* IE9 Compat Modes */.
For a variety of reasons we postponed upgrading our custom CMS from
Rails 2 to 3 for …. quite a while. Migrating on the bleeding edge is
hard - but so is being so far behind. In particular, the advice to
upgrade step by step, using the rails rake tasks to automate some
things, doesn’t really work because it is too difficult to figure out
compatible sets of plugins. For one thing, my testing frameworks have
undergone drastic changes. So even if I can automate upgrading rails,
I can’t run the tests to verify the upgrades. So I am going to just
start over with the current version of rails: 4.1.0.
Using RVM, ruby-1.9.3-p484, and a fresh gemset (empcms4), I generated
a new project. I don’t think turbolinks is going to be especially
useful, so I removed it.
I really like shoulda, so that’s a must. I am less adamant about test
framework. I generally prefer the minitest assertion syntax but I just
spent a frustrating hour trying to use MiniTest::Mock to get a
controller destroy action to claim it failed so I could test the
responder from Chapter 6 of Crafting Rails 4 Applications. So when I
read that Thoughtbot uses shoulda + rspec, I decided to use rspec.
This blog post, Setting up the BDD stack on a new Rails 4 application,
has some pretty good advise on how to set up the things I want to
use. Remember to include ‘rspec-rails’ in both the test and
development groups so generators will create rspec tests for things.
I regenerated the current pages model and added some not null
constraints and default values. I like using FactoryGirl and have a
number of utility methods I would like to transfer from my original
codebase. I installed factory_girl_rails and then configured it as
described in the Getting Started
docs.