I wanted to integrate some branding functionality into an application we’re developing and so I needed test file upload functionality. We’re using Webrat for integration tests, though this will likely change as we increase the amount of Javascript in the app. I added Paperclip to handle the file attachments for logos, and everything was working.
When I added validation to the model, making sure that the file being attached was an image, this broke the tests. It didn’t seem to matter what type the file was, it would fail no matter what on the file type validation.
I used ruby-debug to debug my test and it seems by default Webrat sends file uploads as plain text. It does have the option to specify the file type when attaching the file, so the easiest way around this is just to specify the MIME type for the file. Now my Cucumber step looks something like this :
When /^I attach "([^\"]*)" image to the "([^\"]*)" file field$/ do |filename, field|
type = filename.split(".")[1]
if type == "jpg"
type = "image/jpeg"
end
attach_file field, File.join(RAILS_ROOT, test_asset_path, filename), type
end
Obviously this will need some work as I progress, but it works. At this stage I have an assets folder in my features folder to store any files that I need for my tests.
On the confirmation end of the test I just have a simple tag test to check that the image tag is displaying, and it contains the correct src attribute :
Then /^I should see tag "(.+)"$/ do |selector|
(Hpricot(response.body)/selector).should_not be_empty
end
So in my feature test I have :
Then I should see tag "img[@src*='']"
This just confirms that there is an image tag that contains the file name of the file that I uploaded in the test.
These days I’m using a lot of asynchronous calls to get data and dynamically build the UI on the client side. It generally allows a far nicer experience to be provided to the user, being able to update parts of the UI without reloading the whole page is one of the first steps to your apps being able to wear a Web 2.0 moniker.
The general pattern for me these days has become :
var callback = function CallBack(data) {
... Do Some Processing ....
}
var input_data = GatherData();
MakeRequest(target_url, data, callback);
I tend to use jQuery and so my callback is passed in the returned data from the target_url. My call back function then generally performs some tasks on the UI based on what it receives.
The problem though is that in this pattern you can’t get any data from the context when MakeRequest() is called into the scope of CallBack. It’s a scoping issue that falls outside of this little post, but if you’d like an explanation of how Javascript handles scope of variables then you can Google for Javascript scope chain or take a shortcut to this article. Essentially when the call is made to CallBack() all it will have is it’s own variables and any globally accessible (window) variables.
This week though I had a thought and worked through it with one of my work mates, Tony. If you passed in a function that had the scope that included the context you wanted to pass, then maybe you’d be able to access whatever data from the callee you wanted. It turns out that this does work.
The way to do this is pretty simple, and I used it to create a simple function that would grab data and then populate a select element with options. In this case the context I’d like to keep is which select element is the target.
Say that I needed to run this over a bunch of select elements in quick succession then as the callbacks were issued they may end up out of the initial execution order, so the target element isn’t reliable if it’s been stored in a global variable. I could pass it in as a variable that would come back from the page that is returning the data but that just smells bad to me. I think potentially JSONP is an alternative too, but this felt like the right way.
$.getJSON(url, input_data,
(function(target_element) {
return function(response_data) {
var html = [];
for (var i = 0; i < response_data.length; i++) {
item = response_data[i];
html.push('');
}
target_element.children(':gt(0)').remove();
target_element.append(html.join(''));
};
})(target_element)
);
Essentially the main thing that has changed is that we are now running an anonymous function at the time that the AJAX call is issued. This anonymous function itself returns a function that matches the signature that jQuery is expecting for the callback function. The scope in which this function runs contains the target_element because it was passed into the anonymous function as a parameter. I’m tempted to say that it’s all crazy Javascript scoping, but in reality it’s very cool and very powerful.
If you want to see the execution order of this then just put a some logging into the anonymous function and the callback function and you’ll see what I mean. It will probably make it easier to see what is going on too.
I’ve run into issues trying to get around these problems before and thankfully as mine and the team’s knowledge of Javascript increases I’m finding better and better solutions. I thought my previous method of approaching this problem was quite hacky but now I don’t feel so dirty.
Thanks to Tony too for working through this with me!
This morning I had to solve a problem that involved an application inside an IFrame not keeping it’s session state. I’d solved this problem many times before, a long time ago, in a galaxy far far away, but still it stumped me again!
In this example we had two client websites we’ll call site-a.com and site-b.com. Site-a.com had a page that contained an IFrame which sourced it’s content from site-b.com. The page on site-b.com allowed the user to log in to the application that resides on the site-b.com domain and then click on various links that would take them to other areas in the site-b.com application.
This worked perfectly in Firefox and Safari, but in IE7 it failed and would not let the user log in. After setting up a test environment on my virtual machine and repeating the bug I went looking for a solution.
It turned out that what was required was a declaration by site-b.com that it was okay to do what I was trying to do. It seems that as long as site-b.com declares that it’s safe then the browser assumes this to be the case and it all starts to work.
The way this is done is through adding P3P info to the header of the response. Essentially what this does is say to the browser that your application is okay with taking information from other domains. Rather than relying on external security measures, you’ve taken the steps yourself to develop a secure application. This sort of setting can also be used for single sign on situations too where your cookies need to be accessible across domains and applications.
Typically it’s as simple as adding :
header('P3P: CP="CAO PSA OUR"');
to the page. You could also add it on the server level if required, but in this case it all takes place in an extranet module so it’s perfectly suitable to be applied only on one page. There are quite a few options that you can find on the P3P Specification.
So that’s how you get cross domain cookies to work with PHP. It’s much the same in other web frameworks, you just need to work out how to modify the headers in the one you’re using.
In PHP4 objects were passed by value, it’s probably the intuitive way to deal with variables for a beginner and in a language where objects are not first class. However in PHP5 this has been changed and now objects are passed by reference, this stung me when writing some tests recently.
public function testNameIsUnique() {
$test1 = $this->BuildValidDiscountType();
$test2 = $test1;
$test1->Save();
$this->assertTrue($test1->id > 0);
$this->setExpectedException('DiscountTypeException');
$test2->Save();
}
This code shouldn’t have worked as far as I was concerned, in fact I was expecting an exception to be raised. Instead it was working and after a little debug tour I found that my $test1 = $test2 line was causing $test2 to be a reference to $test1, not what I wanted. This caused my update method to be triggered instead and of course the name is still unique.
It only required a small change to that line, using the clone specifier :
$test1 = clone $test2;
After that everything went as expected and I knocked off another test, and added to my PHP knowledge.
Ajax is a fairly broad technology with so many different client side and server side approaches that it’s hard to provide generic best practice rules. This however is one.
Requests via ajax tend to be handled asynchronously these days, communicating with the server whilst keeping the user interface active and responsive. This approach provides a potential problem though as it is possible to make a request and not worry about results, presuming what was sent just worked.
Better practice is to always respond from the server and at least have some form of simple logging at the client side so you can be certain communication is working as expected. It is simple for the server to respond with JSON in the form of:
{success:1}
This provides several pieces of information. Firstly the server action completed and was not met with an error such as error 500 (server error) or error 404 (page not found). Also if we toggle the “success” value with true or false, depending on the success of the request, we know that the process we requested was successful or failed. In one small line we rule out several potential points of failure.
Note that most major JavaScript libraries provide a simple means of handling success and failure so this is not hard to implement. In the case of Prototype you need only look at the “onError” and “onSuccess” attributes when defining the initial Ajax request.