Archive for the ‘Programming’ Category
December 12th, 2011
I got a request over the weekend to support a client library on the Android platform that cannot submit multipart form data easily. The request was to allow the client to send the raw bytes as the HTTP request body. In order to implement this functionality I of course wanted to spec it out first to make sure I was writing this correctly - it isn't every day that I deal with the raw request body after all.
My spec ended up looking like this...
#./spec/controllers/file_test_api_controller_spec.rb
⋮
describe "#raw_upload" do
before do
# file_path points to a file I used dd to generate that is 11,776 bytes
bytes = File.open(file_path, 'rb').read
@request.env['RAW_POST_DATA'] = bytes
post :raw_upload, { }, 'CONTENT_TYPE' => 'application/octet-stream'
@request.env.delete('RAW_POST_DATA')
end
subject { response }
its "status" do
should == 200
end
its "body" do
should =~ /size.*11776/m
end
end
⋮
To access this data directly in your controller, you just use request.raw_post.
Its worth noting that my controller replies with a receipt in JSON that includes the size of the received data which is why the spec regexes the body for it - there's no automagic occurring with my controller's response.
This approach does not work with HTTP PUT as the RAW_POST_DATA request environment variable is cleared when a PUT is performed.
Reference: How to send raw post data in a Rails functional test?
December 7th, 2011
If you are using a series of button_to UrlHelpers in Rails and want the buttons to line up horizontally on a row you can use this CSS to accomplish it:
/* ./app/assets/stylesheets/button_to.css.scss */
form.button_to {
margin:0;
padding:0;
display:inline;
}
form.button_to div, form.button_to div input {
display:inline;
}
If you are using Rails 3.x's asset pipeline, remember to add a line to your application.css to include the button_to asset - assuming you're breaking out your styles into separate files.
December 6th, 2011
Ran into a simple problem today while I was speccing a new controller in our project. We generate some signed cookies for devices which check into our system and then we use those cookies to identify these devices when they return. Unfortunately the ActionController.TestRequest @request instance variable you get when using rspec-rails has the cookies collection as a basic hash and no cookies= method defined. This means that attempting to invoke permanent or signed on a hash results in a runtime error and you can't just assign the cookies collection to a valid ActionDispatch::Cookies::CookieJar instance.
You can get around this by going around the missing attribute setter and changing the instance variable directly.
#./specs/controllers/some_controller_spec.rb
# a new device is created here...
⋮
@request.instance_variable_set(:@cookies, ActionDispatch::Cookies::CookieJar.build(@request))
# the token is what we use to identify the device on subsequent requests
# so our specs need to be able to create it when necessary
@request.cookies.permanent.signed[:token] = [device.device_id, device.secret]
⋮
# remainder of spec omitted
If you're doing stuff with the regular cookies hash you will need to merge! your cookies hash with the @request.cookies hash before building and assigning the full blown ActionDispatch::Cookies::CookieJar instance.
Reference: How to test cookies.permanent.signed in Rails 3
December 2nd, 2011
There is surprisingly little up to date information about ActionView::Helpers::FormBuilder. That's because mostly it hasn't changed. I'm always afraid of documentation going back to 2008 when we're talking about Rails because 99% of the time its just not quite going to work.
A little bit of background: we're using Twitter's Bootstrap in our project. There's a bit of ceremony around each field such that you need to do a .clearfix div, then a label, then a .input div and finally the input. I want to eliminate all those keystrokes so I can just do:
#./app/views/c2dm/new.html.haml
form_for @message, :url => {:action => 'create'} do |f|
%fieldset
= f.text_field :registration_id
= f.text_field :collapse_key
= f.text_area :message
.actions
%input.btn.primary{:type=>"submit"}
Here's the no-nonsense way of achieving this by adding a new class to your app/helpers/application_helper.rb (don't forget to restart your Rails server when you do this):
#./app/helpers/application_helper
⋮
# add these lines after the ApplicationHelper module
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
helpers = field_helpers +
%w{date_select datetime_select time_select} +
%w{collection_select select country_select time_zone_select} -
%w{hidden_field label fields_for} # Don't decorate these
helpers.each do |name|
define_method(name) do |field, *args|
options_index = ActionView::Helpers::FormBuilder.instance_method(name.to_sym).parameters.index([:opt,:options])
if options_index.nil?
options = args.last.is_a?(Hash) ? args.pop : {} # pretty sure this shouldn't happen
else
options = args[options_index - 1] # -1 to account for the method name argument
end
label = label(field, options[:label], :class => options[:label_class])
@template.content_tag(:div, :class => 'clearfix') do
@template.concat(label)
@template.concat(@template.content_tag(:div, :class => 'input') { @template.concat(super(field, *args)) })
end
end
end
end
ActionView::Base.default_form_builder = BootstrapFormBuilder
# you can also register this in ./config/application in the config block
# but I was not having a lot of success going that way
# which is why I moved it to the ./app/helpers/application_helper.rb file
Ideally you'd add this somewhere outside of application_helper.rb and load it properly.
What I referenced:
advanced rails studio: custom form builder
Form Builders in Rails
Working With and Around FormBuilder
Updated: 12/21/2011 for handling methods where options isn't the last param.
November 2nd, 2011
I have a schedule.json file that I want to dump to the browser for testing purposes.
This took entirely too long for me to sort out through trial and error, but here it is:
#/app/controllers/some_controller.rb
def schedule
render :file => "#{Rails.root}/app/assets/resources/analytics/schedule.json",
:content_type => 'application/json',
:layout => false
end