Content
1 March 2010 //
Filed under
Uncategorized
<rant>
There has been, and continues to be, a lot of talk around writing semantic code. One thing that gets under my skin, and I wish it would stop, is the continued use of #can?(:do_something) as a pattern for handling permissions. I have no problem with the pattern itself, it’s the verb I take issue with. “Can” asks for the ability to do something, “may” asks for permission” Get it right people.
Doesn’t anyone else remember the following scenario from second grade?
Miniature you: “Teacher, teacher! Can I go do the bathroom, please?”
Mrs. WhatsHerName: “I don’t know, can you?”
Miniature, pissed off, you: “Well, yea, I can…” <blank stare />
I’m looking at you cancan, canable and walruz.
If you still don’t get it read this.
As far as I know, the only plugin out there that handles this (semantic issue) correctly is Makandra’s Aegis. And they are GERMAN. It functionally works about the same as well. So props to them for that.
</rant>
1 comment ::
Share or discuss
::
2010-03-01 ::
admin
10 February 2010 //
Filed under
javascript
I did a ground up re-write of my date-time picker. It now only relies on jQuery (1.4.1) and jQuery UI (1.8rc1).
Use case is pretty simple right now:
$(function(){
$('#datetimepicker').dateTimePicker();
$('#datepicker').dateTimePicker({showTime: false});
$('#timepicker').dateTimePicker({showDate: false});
});
See the demo for more details. Enjoy!
::
Share or discuss
::
2010-02-10 ::
admin
25 January 2010 //
Filed under
rails
For one of my apps I’ve developed what I think is a very easy to use and consistent way of handling error and success messages throughout my app. It would be a lie if I called the following list a set of goals since they evolved over time, but there’s definitely a bit of philosophy driving any future changes that I make to this aspect of the system.
1) Error/Success messages should look and feel the same throughout the application. This means that the HTML and CSS that displays them should be located in one place and not scattered across several different views and stylesheets. It also shouldn’t matter if there is one error or a list of errors.
2) I should be able to generate and display these errors whether this is a synchronous or an asynchronous request.
3) Everything should quack.
The Setup
The relevant code from ApplicationController:
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
helper_method :errors_for, :success_for
private
def success_for(message)
unless message.blank?
render_to_string :partial => 'shared/success', :locals => {:message => message}
end
end
def errors_for(object)
unless object.respond_to?(:errors) && object.errors.empty?
render_to_string :partial => 'shared/error', :locals => {:object => object}
end
end
end
Making them helper methods is a necessary evil so that they can be called from both the controller and the view. Calling them from the view should be obvious. Say you have an Active Record object with errors on it:
= errors_for @user_with_errors
…and calling it from inside a controller is extremely useful when you need to pass errors back to an AJAX request:
render :json => {:errors => errors_for(user_with_errors)}
This is where people start to get feisty. “You shouldn’t be passing HTML back with JSON. JSON is for passing raw data.” I say rules are meant to be broken. If I passed the raw data (the errors) back in JSON I would have to create the associated markup in javascript. So not only am I duplicating logic, I’m doing it in two different languages (javascript, ERB/HAML). That sucks.
Here’s my error partial, in HAML:
-if object.is_a?(String) || object.is_a?(Array)
.errorWrapper{:style => "width:#{display_width(object)}px;"}
-if object.is_a? String
%p= object
-elsif object.is_a? Array
%ul
-object.each do |message|
%li= message
-elsif object.kind_of?(ActiveRecord::Base) || object.kind_of?(Authlogic::Session::Base)
.errorWrapper{:style => "width:#{display_width(object)}px;"}
=error_messages_for(:object => object, :id => nil, :header_message => nil, :message => nil)
I know that kind of looks a mess, and some slight duplication, but it’s all for the greater good. First off, the partial renders nothing if it gets passed the wrong Thing. Secondly, it’s wrapped up nicely by errorWrapper, which gives us a consistent place to start writing styles from. We do this since you cannot (currently) change the class that error_messages_for gives you. Thirdly, we’re using error_message_for so that we can seamlessly pass ActiveRecord and AuthLogic objects and it won’t blink.
The success partial is a bit simpler since we don’t expect objects to have “success” messages attached to them. Instead it accepts single strings or an array of strings:
.success{:style => "width:#{display_width(object)}px;"}
-if object.is_a? String
%p= object
-elsif object.is_a? Array
%ul
-object.each do |message|
%li= message
And finally, a bit of “making things look nicer”:
module ApplicationHelper
ERROR_COEF = 8.0
def display_width(object)
(if object.is_a?(String)
object.length
elsif object.is_a?(ActiveRecord::Base) || object.is_a?(UserSession)
object.errors.full_messages.map(&:length).max
elsif object.is_a?(Array)
object.map(&:length).max
end * ERROR_COEF).to_i
end
end
A bit of a mess again, but what we’re doing here is defining a width for the wrapping container based on the length of the error messages. This is so that we don’t just arbitrarily set our container to be 600px wide for errors that are only three words. (This looks pretty awful) You might need to play around with that coefficient a bit to make it work with the rest of your styles — and even that may not be very precise. That said, I’ve yet to run into any error or set of errors that this didn’t handle very nicely.
The Rub
So how do we use all of this? Pretty much any way you want! Throw an errors_for(@object) in your edit template and forget it. If @object has errors on it, they get displayed, if not, then no markup is generated. Not even a wrapping container.
Want to create an array of errors for an AJAX request and display them? No problem.
error_messages = []
error_messages << "Err"
error_messages << "Foo"
render :json => {:errors => errors_for(error_messages)}
Then, in your javascript (jQuery)
$('form#login').prepend(response.errors);
What about success messages? They’re probably passed in some kind of Flash.
Controller:
flash[:success] = "The post was successful"
View
=success_for flash[:success]
This is what I’m doing, how are you handling this problem?
::
Share or discuss
::
2010-01-25 ::
admin
6 January 2010 //
Filed under
rvm
This recently happened to me:
$ rake db:migrate
/Users/jsharpe/.rvm/ree-1.8.7-2009.10/lib/ruby/1.8/openssl/ssl.rb:31: [BUG] Bus Error
ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin9.8.0], MBARI 0x8770, Ruby Enterprise Edition 2009.10
Abort trap
I’m really not sure who the culprit is here. I don’t think it’s REE since it also happens with 1.8.7. So is it 1.8.7? Is it RVM? Openssl? Or is it me? Whatever the case this took me a LONG time to figure out – I hope this post saves many thousands of people countless wasted hours.
Add this to ~/.bash_profile
export RUBYOPT="-ropenssl"
3 comments ::
Share or discuss
::
2010-01-06 ::
admin
13 November 2009 //
Filed under
active record
I personally don’t think this is all that exciting — but I see this question asked a lot and just want something to point at from now on.
Say you have several ‘nested’ has_many relations:
class State < ActiveRecord::Base
has_many :cities
end
class City < ActiveRecord::Base
has_many :streets
belongs_to :state
end
class Street < ActiveRecord::Base
has_many :houses
belongs_to :city
end
class House < ActiveRecord::Base
belongs_to :street
end
How do you get all of the Houses in virginia?
Well, you could do this:
virginia.cities.collect{|c| c.streets}.flatten.uniq.collect{|s| s.houses}.flatten.uniq
... but that's epically lame. It looks like shit and produces an metric ton of queries and as a result is highly inefficient.
How about this:
House.find(
:all,
:include => {:street => {:city => :state}},
:conditions => {'states.id' => virginia.id}
)
This is a single query with joins where appropriate, and it's a finder on House which is what you're getting anyways. Makes sense, no?
4 comments ::
Share or discuss
::
2009-11-13 ::
admin
27 October 2009 //
Filed under
active record
Every once in a while I see this little code snippet show up somewhere:
script_console_running = ENV.include?('RAILS_ENV') &&
IRB.conf[:LOAD_MODULES] &&
IRB.conf[:LOAD_MODULES].include?('console_with_helpers')
rails_running = ENV.include?('RAILS_ENV') &&
!(IRB.conf[:LOAD_MODULES] &&
IRB.conf[:LOAD_MODULES].include?('console_with_helpers'))
irb_standalone_running = !script_console_running && !rails_running
if script_console_running
require 'logger'
Object.const_set(:RAILS_DEFAULT_LOGGER, Logger.new(STDOUT))
end
You see, if you drop this little gem in ~/.irbrc you’ll start getting the SQL output from your various ruby commands. This is an excellent debugging tool, but it’s an even better learning tool.
To the novice the difference between the following two statements may not be readily apparent…
User.find(:first).posts.each{|p| p.comments.do_something}
User.find(:first, :include => {:posts => :comments}).posts.each{|p| p.comments.do_something}
…but as soon as they sit and watch what scrolls by in the terminal they will *very* quickly realize that one of them is *very* wrong.
I submit that the next time you are teaching anyone the basics of AR that this be the very first thing that you introduce them to.
::
Share or discuss
::
2009-10-27 ::
admin
22 October 2009 //
Filed under
authentication
A basic authentication scheme should go to some length to do a little bit of remembering in the event your user hits a restricted page before they are actually authenticated.
First, some context. I’ve got this in my ApplicationController:
def require_user
unless current_user
store_location
redirect_to login_path
return false
end
end
def store_location
session[:return_to] = request.request_uri
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
… which allows me to do this in any of my controllers:
before_filter :require_user, :except => [:index, :show]
… and this is cool because now my users get redirected back to where they were going after they log in. I like saving my users a click or two here and there if at all possible.
There’s a problem with this though. What happens if somehow your user POSTs to an action that is behind require user? Well request.request_uri is *not* what you want to redirect to. Why? Because redirect_to is going GET the same url that your user was trying to POST to. And if you’re being a good Rails developer and using resources then you’re just going to smack them in a face with a big fat 404, and that’s not very nice.
I actually managed to do all of this over at github earlier today by trying to comment on a commit without being logged in. Want to see it in action? (until github fixes their ways) First, go to github and logout. Then find any commit and try to make a comment, you can figure the rest out. Go ahead, I’ll wait.
What is that critter anyways? It’s like a half octopus with a lizard tail and whiskers. wtf.
Anyways, here’s the simple fix:
def store_location
session[:return_to] =
if request.get?
request.request_uri
else
request.referer
end
end
If they’re GETing something let em keep on GETing it, otherwise take them back to where they were coming from.
This solution is only half-baked though. Any form data that the user has filled out will be gone and they’ll have to redo it. On the other hand you really shouldn’t be showing a form to an unauthenticated user that requires them to be logged in to hit it. If you really insist on doing that have fun saving all those parameters in the session or somethin in between requests. I’d imagine that’ll suck.
1 comment ::
Share or discuss
::
2009-10-22 ::
admin
9 October 2009 //
Filed under
tests
Here’s a little bit of rcov love to ensure that your test suites are covering what they should be.
http://gist.github.com/206379
This should give you:
rake test:rcov
rake test:rcov:units
rake test:rcov:functionals
rake test:rcov:helpers
…with a tailored coverage report for each.
Hopefully this can give you a better idea of where your tests are lacking. As it turns out, despite my app having coverage of 96%, my unit tests only cover 86% of my models. I guess that means I have a bit of work to do.
There’s some app-specific stuff in EXCLUDED_FILES but the rest should be relatively application agnostic.
Enjoy
::
Share or discuss
::
2009-10-09 ::
admin
21 July 2009 //
Filed under
javascript
A basic authentication scheme should go to some length to do a little bit of remembering in the event your user hits a restricted page before they are actually authenticated.
First, some context. I’ve got this in my ApplicationController:
def require_user
unless current_user
store_location
redirect_to login_path
return false
end
end
def store_location
session[:return_to] = request.request_uri
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
… which allows me to do this in any of my controllers:
before_filter :require_user, :except => [:index, :show]
… and this is cool because now my users get redirected back to where they were going after they log in. I like saving my users a click or two here and there if at all possible.
There’s a problem with this though. What happens if somehow your user POSTs to an action that is behind require user? Well request.request_uri is *not* what you want to redirect to. Why? Because redirect_to is going GET the same url that your user was trying to POST to. And if you’re being a good Rails developer and using resources then you’re just going to smack them in a face with a big fat 404, and that’s not very nice.
I actually managed to do all of this over at github earlier today by trying to comment on a commit without being logged in. Want to see it in action? (until github fixes their ways) First, go to github and logout. Then find any commit and try to make a comment, you can figure the rest out. Go ahead, I’ll wait.
What is that critter anyways? It’s like a half octopus with a lizard tail and whiskers. wtf.
Anyways, here’s the simple fix:
def store_location
session[:return_to] =
if request.get?
request.request_uri
else
request.referer
end
end
If they’re GETing something let em keep on GETing it, otherwise take them back to where they were coming from.
This solution is only half-baked though. Any form data that the user has filled out will be gone and they’ll have to redo it. On the other hand you really shouldn’t be showing a form to an unauthenticated user that requires them to be logged in to hit it. If you really insist on doing that have fun saving all those parameters in the session or somethin in between requests. I’d imagine that’ll suck.
::
Share or discuss
::
2009-07-21 ::
admin