Using define_method to overwrite a class' initialize as an example of overwriting a method from your mock, version 2 1
Last year, I wrote an article about redefining an instance method from a module via alias_method. I understand better how extend/include work together now (and the ClassMethods include patterns and whatnot) so I am little embarrassed at my earlier confusion, but there is one concrete thing I wanted to add/simplify.
If you are mocking and don’t need to keep a reference to the old method, using define_method might be marginally cleaner.
def self.included(associator_class)
associator_class.send(:define_method, :initialize, instance_method(:initialize))
end
This is handier because you can skip the method definition/reference logic and just do it in situ, for instant-stubification
ClassToBeIntercepted.send :define_method, :method_to_be_nuked, proc {return 'nuked'}
Validating that at least one attribute in a list is present: validates_presence_of_any
module CustomValidations
def validates_presence_of_any(*attrs)
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
attrs = attrs.flatten
send(validation_method(options[:on] || :save)) do |record|
return if options[:if] && !evaluate_condition(options[:if], record)
attr_list = attrs.map{|a| a.to_s.humanize}.to_sentence(:connector => "or", :skip_last_comma => true)
record.errors.add :base, "Either #{attr_list} needed." if attrs.all?{ |attr| record.send(attr).blank?}
end
end
end
and then “extend CustomValidation” in your model.
Then you can
validates_presence_of_any :first_attr, :second_attr, :third_attr
Thanks to bitsweat on #caboose for help with a succinct name that follows the convention.
Doing a rails custom validator with-out using validates_each
def self.validates_your_funky_non_validates_each_using_way(*attrs)
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
attrs = attrs.flatten
# Declare the validation.
send(validation_method(options[:on] || :save)) do |record|
return if options[:if] && !evaluate_condition(options[:if], record)
#put real code using attrs and record here
record.errors.add :base, "monkey", if record.primate?
end
end
rails javascript helper - capturing an additional level of block nesting within a content_for
When making a helper that takes a block, the yield causes it to output at the point of being called in the template. If the reason for the helper is to populate a content_for/yield variable, such as to put javascript in the head tag, you get duplicate output: once where it belongs when the content_for is yielded, and once where you call your helper. Instead, make use of capture(&block).
This helper
def javascript(&block)
content_for(:header) do
"<script type=\"text/javascript\">//<![CDATA[#{capture(&block)}//]]></script>"
end
end
when called like this
<%javascript do%>
alert('hi')
<%end%>
results inthis
<html>
<head>
<script type=\"text/javascript\">//<![CDATA[
alert('hi')
//]]></script>
if you have a yield :header in your head tag.
Getting camping working with fcgi on a shared host (DreamHost), part 1
Since I’ve been splitting up an app into an rails admin section and a separate camping front-end, I need to get camping working on the host to deploy. And that host, for this app, happens to be DreamHost, which means it’s gotta be fcgi. I’m not all the way done with that, but after a late night last night, I managed to get it running on fcgi locally. I’ll skip over the basics (for one thing, if you haven’t gotten this far yet, you are going to need to struggle through it to be familiar enough with what is happening to have a chance of finishing the process) until I have it all worked out smoothly, but I have to post a couple highlights, to spare some people with the same pain.
First off, no luck running off of edge. The only way I got it working consistently with mongrel, cgi (crucial for debugging and figuring out why things aren’t working), and fcgi, was to use the gem release (1.5.180), and patch that (keep reading). Once I had that running smoothly with the built-in server and then cgi, I got it working with fcgi. If you have the same problem I did, you’ll get an ”`camp_do’: undefined local variable or method `exc’” error. THis is because there is another problem, which is throwing an error, but there is a bug in the exception handling code. Manually fix that (change e on to exc in the exception catching, so that it is defined), and then you can move on to the next issue, since your exceptions will now actually get through to the log, or the screen, depending on how far you’ve made it, and you can use them for more debugging.
If you have my problem, you can track down the next error to the path variable not being populated right in the fastcgi.rb
def camp_do(req)
root, path, dir, app = "/"
if ENV['FORCE_ROOT'] and ENV['FORCE_ROOT'].to_i == 1
path = req.env['SCRIPT_NAME']
else
root = req.env['SCRIPT_NAME']
path = req.env['PATH_INFO']
end
Wether or not I set ENV[‘FORCE_ROOT’] the wrong server variables are being set for camping to know the right path. This shows up as gsub on nil errors, or ”/dispatch.fcgi not found”, or some others, depending on exactly which way you are running things. This all comes back to not being able to use ScriptAlias, as in the wiki, due to only having .htaccess level access. And using RedirectRule and other mod_rewrite stuff that workins in .htaccess (I actually just stole that right from my rails .htaccess) populates things a little bit different.
My fix was to set “ENV[“FORCE_ROOT”]=1.to_s” in my dispatch.fcgi, and then change the corresponding “path =” line to match what my set-up was populating -req.env[‘REQUEST_URI’]. I tracked this down by getting things running well enough to get the default error dump to the browser, and then checking over that.
Now I need to duplicate this without using global gems (since I can’t upgrade them on the shared host), and the do more debugging on the actual host.
Getting ssl and redirect_to working without custom proxy headers
Since HTTP redirects are required to be fully qualified absolute paths, rails prefixes the host and protocol. And if you are using named routes _path, which returns non qualified paths, so it just checks what that protocol of the request is, and uses that. This is fine, unless you are serving https:// and you are operating from behind a proxy, in which case rails can’t know the original request was ssl so it prefixes the redirects with http:// . As a fix for that the core team added a check for the “X_FORWARDED_PROTO” header which you can add in your proxy – if you are running Apache 2.
If you are on Apache 1.3 there is no way to do this (RequestHeader is an Apache 2 only feature), so you are kinda stuck. On top of this, if you try using the spiffy ssl_requirement plug-in to enforce ssl where you want, the request will get caught in a looping redirect, since it will never know that the page is already being served up on https.
Jamis Buck wrote a work-around for serving some actions as ssl, but I wanted a simple way to have my whole site work under ssl. After digging around through the rails code, I realized you could just override the ssl? check on the request and force it to true. Of course, the request has to exist already, so I did it in a before filter.
class ApplicationController < ActionController::Base
before_filter :set_ssl
private
def set_ssl;
def request.ssl?; true; end
end
end
You still have to set-up Apache to not serve up non-ssl, since there is no way for rails to tell the difference, but this will cause all your redirect_tos to use https://, which is the tricky part, since it’s easy to make everything else use path_only links.
I’m going to see what the boards think about honoring default_url_option[:protocol] on redirect_to(string) to obselete this, but I’ll upate if that happens.
*Or any other non fully-qualified redirecting, such as redirecting back to an original requested page, after a login, based on request_uri)
redirect_routing plugin : easy redirects in your routing and my patch accepted
Lars Pind’s redirect_routing plugin fills a gaping whole in rails routing’s claim to be an apache mod_rewrite replacement. With a simple
map.redirect 'old_path', the_usual_options_like_controller_and_action
you can handle redirecting old urls to the new rails functionality, or whatever else you want to do a redirect for.
My only problem was, since the plugin creates a controller to do this (behind the scenes, of course), it raised a “missing default helper path” warning on starting up the server. That was easily resolved by adding an matching, empty helper, and then submitting as much as a patch, which Lars applied pretty much immediately, so it seems he’s on the ball.
A separate admin app, aka sharing a database between rails and camping 1
I realized that pretty much all of my controllers for a certain rails app were the basic rest actions + public_show and public_index. I thought about it and realized it would be easier all around if those couple actions were just spun out into a different app. Then the admin app could be pure rest, with no extra actions and the various settings that differ between the two (ssl or not, sessions on or not, login required or not, read-write or read-only database access) could be set at the app level, instead of per action. All at once my app would be much simplified, and as a bonus, more secure. And there are obvious scaling/performance benefits to splitting them up.
After getting most of the way through this, I have to say I really like camping, and doing things this way has definitely simplified both codebases. I didn’t realize how little was actually shared between the two aspects. At first I was going to try and share the model code between the two apps, and deal with the hassle of merging back and forth, or having a symlink to shared folder (I have been playing around with svk again, for my disconnected/laptop development, and that means no svn:externals – I’ll write later how I’ve decided that is mostly a useful constraint), but then I realized even my models files could be partitioned better. Some models weren’t even needed on the front-end, on the ones that were most of the relationships/associations weren’t, and on the rest most of the methods were either/or public/admin.
So all of a sudden all models (not to mention the controllers) on the front-end became, much, much slimmer, to the point of just usually just being “class ModelName < Base; end” or maybe one association or a finder or two. Obviously this is easier to test, secure, and maintain. The only hiccup was that camping assumes the possibility of multiple apps per site, and thus prefixes the app name to the table. While you could make database views named app_model, for camping to access, it was simpler to just overload and cancel out the prefixing:
module MyApp::Models
def Base.table_name_prefix; end
As, I’ve discovered, one of the benefits of going camping is it encourages you to simplify the code – to the point of making things short one-liners. I also think that it’s helping my ruby skills to do more development outside of rails, even if it is still in a similar venue. For example, since I couldn’t figure out how to do the rails style content_for/capture and multi-yield, I had to be more creative with blocks in my partials (which, just being methods like any other view, can be called with a block – I’ll write about that later, too). Also for smaller apps, or parts of them, I really like the simplified routing/controller/link-generation trifecta. Oh, R(), how I love thee. I think Rails could actually learn a thing or two about the possible simplifications, but then again you can just use camping when rails is overkill.
Rails environment files and non-config config
config.after_initialize do
Markaby::Builder.set(:indent, 2)
end
A quick note on some awesome Ruby and/or Rails books - aside from Ruby for Rails crappy DRMed ebook handling
I’ve been busy reworking my site to be more friendly for the gig/job hunt (adding a resume and some other content and reorganizing in general – not quite live yet), so I haven’t been posting much lately. I’ve still been reading and working on some other stuff while I’m not doing that, though, and I just started on The Ruby Way. My first Rails book was the Agile one (no link, if you don’t know what I am talking about, this post probably isn’t for you) as it was for many others, and then moved onto Ruby For Rails at Giles Bowkett’s suggestion. Now that I am on the third in the trifecta, I have to say he is totally on the mark.
One aside, though, avoid the Ruby for Rails e-book version like the plague. The other two are very nicely handled by their publishers as a simple pdf download, but for some reason the Ruby For Rails version is a crappy DRM’ed “ebook” that has to be read in Adobe’s even crappier ebook reader, instead of whatever you normally read your pdfs in.

Articles via rss or email