augmentin ile ilgili haber gabapentin 2 cream efectos de cytotec a largo plazo viagra falls review metronidazole cream eczema zovirax cold sore cream usa buying cialis on bangkok street como se debe tomar misoprostol bactrim induced hypoglycemia orlistat treatment obesity synthroid wellbutrin interaction prednisone cancer de la prostate can doxycycline hydrochloride get you high nexium colaterais wie lange darf man viagra nehmen nexium and esophageal spasms donde comprar misoprostol medellin can zoloft affect your libido ciprofloxacin 3a4 inhibition fatal hypoglycemia associated with levofloxacin headaches after taking viagra taking clomid first cycle after miscarriage how many mg levaquin uti propecia effectiveness old man que pasa si dejo el finasteride lipitor and kidney damage switching gabapentin to pregabalin buying viagra in guatemala cheapest no prescription cialis topiramate package insert can i take 450 mg of wellbutrin sr uso indebido de misoprostol zoloft and itching price for ciprofloxacin 500mg phenergan cream side effects use of viagra and cialis together can prilosec be taken with lexapro chlamydia treatment for men azithromycin olanzapine haloperidol senile delirium allegra rusty magic lamisil tinea pedis sildenafil hotfrog paxil kullanim dozu shingles outbreak valtrex furosemide kidney disease misoprostol para tratamiento aborto incompleto does wellbutrin contain lactose sertraline ginkgo how long will 150 mg diflucan for five days stay zoloft good experiences can low dose doxycycline interfere with menstrual cycle high dose of topamax lamisil cream wholesale overdosering av nexium synthroid bhrt cena xenical w uk alili diet pill dexamethasone conversion prednisone zithromax dry skin what is doxycycline related to viagra how long for half drug interactions between celexa and geodon levofloxacin in dextrose amoxicillin course of treatment what is the antibiotic cipro used for duloxetine side affects fatigue zoloft and extreme tiredness norvasc and inderal viagra in farmacia senza ricetta medica propranolol and thyroid hormones amount of cialis equivalent to 50mg viagra neurontin side effects webmd hyst?rosalpingographie et clomid fda celexa elderly side effects of lisinopril stada 5 mg orlistat xenical works clomid nie dziala tamsulosin med viagra f?r m?nner kaufen topamax suisse baclofen schwitzen buy cialis lowest price cipro for mac kamagra super uk online sertraline 50 mg tabletas efectos difference between cialis 10 mg and cialis 20mg does levitra keep you erect after ejaculation can you take effexor with lexapro ciprofloxacin ear drops uk nexium and protonix are the same gabapentin on full stomach viagra taken by younger men augmentin parodontite xenical diary cialis suppliers in nz tamoxifen er negative breast cancer what are montelukast tablets taking celexa and cymbalta drinking alcohol while on ciprofloxacin flomax dosage bph donde puedo comprar cytotec en guadalajara kamagra classifieds zithromax effects birth control will avodart be approved for hair loss doxycycline periostat dosage topix tadalafil tetracycline therapy of chronic lyme disease what is the genetic tablet for lamisil ciprofloxacin breast milk lasix for dogs reviews zocor with lamisil strep b and amoxicillin wirkung von azithromycin 500mg generic viagra and cialis paypal prednisone to treat muscle pain ingredients for natural viagra salon allegra fresno ca tamoxifen side effect diarrhea meaning strattera omega 3 do u test positive for clomiphene citrate nizoral drugstore awesome viagra bull prednisone 10 mg take coming off zoloft rage levaquin azithromycin drug interactions lasix toedienen na bloedtransfusie can i take flomax while breastfeeding bactrim syrup price tamsulosin blutspende cheapest xenical online uk buy viagra los angeles acyclovir tablet pregnancy does seroquel increase serotonin mode action plavix bactrim oral food cialis to get over performance anxiety remedio singulair valor disadvantages of using tadalafil looking for herbal viagra on buy and sell in ireland clopidogrel pdf nursing implications of zyvox viagra 420 does lisinopril afects mens sperms yo get pregnat nexium feet swelling how soon side effects appear with cialis immediate side effects from lexapro bupropion hcl bupropion sr clopidogrel patent how to tell how much insurance will pay on viagra vardenafil and glaucoma wirkungsweise von orlistat wellbutrin sr compared to wellbutrin xl carbamazepine and levothyroxine nexium health warning 2012 how much does flagyl cost at walmart without insurance amiodarone and doxycycline cialis and painkillers interaction how to give azithromycin to cats side effects of 40 mg of prednisone dogs can i take levaquin and prednisone together albuterol treatments how often how long before fluconazole side effects leave manufacturer of prednisone side effects after stopping prednisone in dogs most common dosage lipitor ciprodex dexamethasone periactin other uses can you take zofran after drinking price singulair without insurance fasten diet pill terbinafine lactation risque fc clomid viagra india store. azithromycin staphylococcus aureus is pristiq better than celexa sertraline approved nursing informtaion on celeaxa and lexapro lipitor and sore tongue viagra prescription walk in clinic mississauga viagra cheapest in birmingham uk what dosage to take prednisone does prednisone give you yeast infection buspar 5mg information treating tamoxifen side effects can i take ibuprofen with ketoconazole substitute for zithromax in the philippines cytotec precio en guayaquil what are bactrim ds tablets used for albuterol sulfate with nebulization adalat composi?ao prednisone for croup dose can you cut zyvox metronidazole in combination with amoxicillin celexa and anxiety disorders bactrim and multivitamins mixing escitalopram alcohol pot dose cipro side effects leg cramps recreational use of cyproheptadine what will happen if i drink alcohol with lexapro how do you take clomiphene clomiphene 59 mg viagra instructions in tamil will amoxicillin help with flu can you take two 5mg of cialis instead of one ten zyprexa svimmelhed 2.5 mg cialis form costo confezione di cialis singulair combate a urticaria nexium magnesium supplements best canadian pharmacy generic viagra valtrex once a week flying nolvadex when to take 40mg of cialis pakistan india viagra how to take doxycycline without throwing up propecia difference of 1 mg and 5 mg maxalt benefits uses of celebrex medicine seroquel 200 mg costo lipitor copay offer.com maxalto showroom how long does it take for 20mg levitra to work can you ovulate more than once on clomid spiramycine metronidazole mal de dent comprar cialis pago paypal spc of olanzapine orally disintegrating tab 15mg does walmart sale lamisil cream how does periactin work in serotonin syndrome phenergan tablete symptoms of thyroxine excess levitra 10mg street price monuril diflucan metformin ct lactic acidosis is 2.5 mg cialis daily is enough flagyl starts working what happens when you take too much celexa buy generic viagra soft tabs 50mg clomid nolvadex and testosterone stimulation can i take lexapro every other day dove comprare viagra senza ricetta milano is amoxicillin trihydrate safe for infants can i take alcohol with prednisone effectiveness of taking viagra under tongue buspar customer reviews does walgreens have vardenafil hcl difference between zithromax and amoxicillin for cats symfona orlistat precio cialis mixed with beta blockers ovulation predictor and clomid how does your voice sound on zyprexa does viagra give a better erection cipro ha partita iva gabapentin for acute and chronic pain mucosa study misoprostol dmaa and zoloft amoxicillin for stuffy nose ciprofloxacin culture dosage for doxycycline in dogs montelukast teva italy patent azithromycin z pack std wellbutrin rash common mypillhealth products men health generic viagra order cialis how long do it take to work 2 clomid par jour can you drink orange juice with azithromycin going back on premarin is lamisil a good choice for toenail fungus inderal for mav common side effects of oral prednisone norfloxacino e levofloxacino pcos clomid metformin effect rash bactrim allergy cialis y peyronie gabapentin 300 mg for opiate withdrawal orlistat fezes celexa and doxepin effetti benefici del viagra is it safe to take generic cialis zoloft pregnancy drug category paxil and triglycerides medicacion tdah strattera cialis essai erection pills canada manitoba in a store viagra special offers septra ds effects birth control paxil and alcohol blackout 200mg viagra dose biverkningar med lipitor why do girls take viagra how many mg of nolvadex for pct rash after a week of taking amoxicillin how much does metoprolol cost without insurance 100mg doxycycline dosage costa allegra ?stliches mittelmeer singulair 4 mg fiyati levitra with entenze tamoxifen on a cycle zoloft or klonopin cialis per ?berweisung bestellen lisinopril dmd fluconazole 150 mg atorvastatin itchy skin side effect lexapro finnland levitra kaufen lexapro trigger bipolar famciclovir image dicloxacilina o ciprofloxacino is their any over the counter viagra is maxalt stronger than imitrex is allegra drowsy bruce roth atorvastatin esertia y viagra buspar and beta blockers the family markowitz allegra goodman summary viagra spray in uae la taverna allegra mugnano adalat para contra? sildenafil soft tablets 100mg what is egg growth using clomid50mg legal take kamagra abroad does zyprexa go off patent amoxicillin sandoz safe for pregnany how much viagra to take for few hours of hardness planet zovirax pfizer viagra safely doxycycline resistance africa dose of lioresal elixir is prednisone 20 mg dangerous for long term diflucan 150 confezione nottingham viagra can you mix klonopin and buspar can i take cipro for a tooth infection viagra 800mg side effect where to purchase viagra over the counter viagra hangi doktor yazar where do i get viagra in seattle alcohol amoxicillin clavulanate potassium kamagra jelly premature celexa withdrawals how long does it last lisinopril teva wirkstoff viagra price in asda viagra online malta metronidazole urticaria kamagra 100 nebenwirkung doxycycline side effects kidney pain nombre generico finasteride atrial fib and celebrex paypal viagra canada paypal clomid success with low amh pcos can benadryl be taken with cialis can i breastfeed while taking flagyl amoxil w ciazy does viagra work on everybody lisinopril and b12 vascular desease and viagra is nizoral shampoo sold over the counter nizoral at walmart serum sickness azithromycin celexa bloodstream lasix 40 mg canine dose zoloft and midol complete will doxycycline mono 100mg give me a yeast infection singulair plicuri 4 mg pret when to take robitussin on clomid quetiapine sore throat doxycycline experiences viagra euroclinix azithromycin for sinus infection clomid angry does viagra cause ringing in the ears clomid pour b?b? 2 which is better propecia or provillus sale nero di cipro dove acquistarlo clopidogrel in primary prevention glucophage medlineplus doxycycline hyclate dosage amount koliko kosta kamagra u bosni i hercegovini mint ciprofloxacin lipitor side effects breast paroxetine zc 15 comprar viagra brasil paxil causes acne ketoconazole or ciclopirox levaquin transaminitis know zyprexa working propranolol glucose levels h pylori treatment cipro viagra visa usa metformin low hdl can metronidazole cause fatigue proscar vancouver does lasix effect in future pregnancy doxycycline and acne scars prednisone and zinc lexapro effect on dopamine best way to conceive twins on clomid paxil patch real cialis in jodhpur para que sirve glucophage xr 500 what to expect after first dose of lexapro olanzapine risk of diabetes azithromycin cyp450 3a4 how long does it take for 2.5mg cialis to work clomid and ovulation timing doxycycline expiry dates cialis professional at online in bangalore cialis no good anymore zithromax 500 mg for 3 days apa efek samping viagra zovirax compresse herpes seroquel muscle soreness nexium generic prevacid proscar ricrescita capelli does diflucan work better than monistat glucovance and glucophage flomax com thyroid tests on synthroid does lexapro 10 mg get you high sildenafil citrate mouth melting strip why should flomax be taken 30 minutes after a meal augmentin et dents jaunes cost premarin cream can you conceive with viagra does bupropion contain maoi propecia going generic in u.s synthroid 275 mcg singulair bb where can i buy cialis in a store in shanghai hur funkar strattera ppi clopidogrel interaction mhra is 25 mg zoloft effective for anxiety levitra viagara topamax od can azithromycin be used to treat tooth infection cialis free sample no prescription seasonique and topamax interactions losing weight while on metformin sudafed and wellbutrin xl bactrim reaction after 8 days viagra patent expiration usa viagra time to take effect cymbalta indicaciones contraindicaciones misoprostol and voltaren paxil cr side effect prednisone causing hives clomid et duphaston et pas de regle flagyl side effects toddlers amoxil reaction to lamisil kostet roche xenical 120mg buy doxycycline dose as antimalarial costo cialis 5 mg is celexa good for you atorvastatin calcium trihydrate partition coefficient affiliate of viagra er det lov ? kj?pe viagra i norge soft levitra viagra cheap canada viagra manchester ageless male and cialis interaction how long may the effect of sildenafil treatment of prednisone withdrawal symptoms what happens if i take nicin and viagra together pengalaman pake viagra dosis ponderal de ondansetron can you split premarin 10mg lexapro equal 50 mg zoloft amoxil without prescription over the counter uk buying viagra online illega how long do liquid azithromycin last doxycycline hyclate skin sensitivity clomid risk multiples seroquel and brain fog bactrim ds efectos secundarios metronidazole digestion genesis finasteride 1mg reviews bupropion side effects forum company that invented viagra viagra on line reviews lexapro weight gain 5 mg famciclovir 500 mg buy prednisone tablets how supplied nifedipine male infertility can i crush my cialis iui and clomid success in single women wechselwirkung pille und metoprolol plavix italiano periactin solucion cipro prices walgreens nolvadex gynecomastia before after buy viagra in edinburgh scotland now strattera similar to wellbutrin dosage of doxycycline for ear infections recreational levitra use plavix drug group viagra ohne rezept kaufen legal cymbalta safe while pregnant indian orlistat tablet name price details cialis daily cena compare doxycycline 50 mg with doxycycline 40 mg fluconazole tablet pregnancy finasteride e linea frontale code like water, compile like bamboo! — Glenn Fu

CORS + Heroku + Passenger + Cloudfront

I host Farmstand on Heroku, and I prefer to use Passenger to run it. I was using the asset_sync gem to connect my assets to Amazon S3 + Cloudfront, but recently the user-env-compile feature was removed and now it’s suggested that you have CloudFront pick up the assets directly from your server instead of sync’ing them there yourself.

This sounded like a reasonable thing until I realized that you can’t set an origin policy in this case. If you follow the suggested setup you’ll discover that CloudFront sets the headers exactly as it receives them from your app. That means if you want your CORS-happy headers on your fonts, your server must deliver them with those headers. Some people will point out that you can set default headers in Rails 4, but running curl -I http://0.0.0.0:3000/assets/super-cool-font.ttf was showing none of those headers, and just some information about Nginx. The reason was that for precompiled files, Nginx would see them, and serve them, and never even let the Rails server know that the request had happened, giving no opportunity to add the custom headers.

FireFox was showing me:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://d1bej2a8uvz4or.cloudfront.net/assets/ss-pika-02874436ebea92b029d8015ec170991f.woff. This can be fixed by moving the resource to the same domain or enabling CORS.

The issue was that the Nginx template provided by Passenger containing this:

location @static_asset {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
}

Recently, as in last month (March 17), Passenger added support for providing a custom template for Nginx in response to this very issue.

Now as long as you ensure your Passenger gem is >= 4.0.39, you can run
ls $(passenger-config about resourcesdir)/templates/standalone/config.erb
to see what the default template is, and make your own version. I put mine in ./config/config.erb, and then in my Procfile changed my web line to
web: bundle exec passenger start -p $PORT --max-pool-size 3 --nginx-config-template ./config/config.erb.

Now you can add the needed headers to that same section of the template to look like this:

location @static_asset {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Request-Method *;
}

In my staging.rb/production.rb file, I can use:

config.serve_static_assets = true
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

To get a really close-to-actual test in my local environment, I ran:

RAILS_ENV=staging bundle exec rake assets:precompile
RAILS_ENV=staging foreman start web

and then:

$ curl -I http://0.0.0.0:5000/assets/ss-pika-02874436ebea92b029d8015ec170991f.woff
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Allow-Origin: *
Access-Control-Request-Method: *
Cache-Control: max-age=315360000
Cache-Control: public
Content-length: 22404
Content-Type: application/x-font-ttf
Date: Tue, 13 May 2014 15:18:34 GMT
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Last-Modified: Fri, 02 May 2014 20:55:47 GMT
Server: nginx/1.6.0
Connection: keep-alive

Hooray, the headers are there!

Mac OSX Development Environment Setup after Archive and Restore

After a few years of owning my Mac, I started to feel that maybe, just maybe, it might be a little slower than it used to be. If nothing else, too many failed late night experiments had led to me installing weird systemy things that didn’t always entirely want to go away.

To clean up my baby, I tried out the ‘Archive and Restore’ feature found on my Mac DVD. It reinstalls OSX Leopard replacing the old instance and attempts to leave all of your application and profile settings just how they were.

Upon completion, I am certainly running smoother and faster, but sadly some of my web development setup got broken along the way.

So that it’s clear, my environment involves using these primary components:

  • MySQL
  • Ruby on Rails
  • Apache/Passenger

Here’s an account of what I did to restore my environment to working order:

MySQL, Git

First step, I followed Dan Croak’s setup guide for the following steps (Ruby and RubyGems were already working fine btw):

  • XCode Tools
  • MySQL
  • Git

Apache/Passenger

Then I recovered the following files from my Time Machine backup:

  • /usr/local/mysql/data (to get my dev databases back)
  • /etc/hosts (to get my local ip shortcuts back)
  • /etc/apache2/httpd.conf (to get my apache/passenger settings back)

If you didn’t backup your Apache/Passenger config, you’ll want to re-run

sudo passenger-install-apache2-module

and probably follow John Ford’s Apache setup guide to get Passenger working for you again.

TextMate integration

Redo this for TextMate’s mate command in console:
http://blog.macromates.com/2005/textmate-shell-utility-tmmate/

locate

I also consulted Neil Ang’s blog to get locate working again.
There’s also a way to make it use Spotlight’s index if you check the comments, however this prevents searching directories such as /usr and /bin, but it can be fun if you want locate to follow your Spotlight preferences for search restrictions.

That’s it! Now I’m back to work. I hope this helps someone else out there.

Development Tips using Passenger for Rails

I’ve just recently set up my rails development environment to be managed by Passenger, and I have to say I like being able to type in real URL’s for my domains and not have to manually start my sites.

However, having formerly done my development by first running “script/server” and then going to town, I miss having the log output handy for whenever I blow something up.

If you’ve made the transition to Passenger I imagine you might also feel the same way, so let’s get that back! The easiest way is to use

tail -f log/development.log

Much better! Now we can see the server log output again so it’s easy to view the stack traces for our bugs. However, now that we’re up and running with our super slick hyped-up toy called ‘Passenger’… we’re still having to take just as many steps to get the same results! In fact, typing that command takes longer than script/server used to take.

Well let’s just fix all of our problems in one fell swoop. Add the following to your .bash_login file:

alias go='cd ~/Sites/newcoolsite.com;> log/development.log;mate .;open -a FireFox http://newcoolsite/staff;tail -f log/development.log;'

…where ‘newcoolsite’ is the directory/domain that you’re interested in. Now when we open up our terminal, we just type ‘go’, hit enter, and in about half a second we’ll find the following has happened:

  1. Our terminal has moved to the proper folder for our site (~Sites/newcoolsite.com)
  2. The development log is cleared
  3. TextMate opened our code
  4. FireFox opened our site (http://newcoolsite/)
  5. Our terminal started monitoring the server log output

Much better! However there’s still one thing bugging me. We used to just hit Ctrl+C then Up and Enter to restart script/server. It was a very quick and easy way to restart the server and keep our terminal window showing us the activity that we want to see. At this point we still have to type

Ctrl+C
touch tmp/restart.txt
tail -f log/development.log

Unacceptable!! We can easily remedy this situation. Just add a new line to our .bash_login file resulting in the following:

alias go='cd ~/Sites/newcoolsite.com;> log/development.log;mate .;open -a FireFox http://newcoolsite/staff;tail -f log/development.log;'
alias rs='> log/development.log;touch tmp/restart.txt;tail -f log/development.log;'

Now we just hit Ctrl+C, then rs to restart the server and resume our server log monitoring.

Very smooth and painless. Happy coding!

Building Named Scopes Conditionally

I recently ran across an article at Caboose.se about building named scopes conditionally to make a slick filter. Unfortunately comments on it are closed now. The code he shared looked like this:

def index
  scopes = []
  scopes << [ :by_user, params[:user_id] ] if params[:user_id]
  scopes << [ :tag_name, params[:tag_name] ] if params[:tag_name]
  scopes << [ :rating, params[:rating] ] if params[:rating]

  order = { 'name' => 'videos.name ASC' }[params[:order]] || 'videos.id DESC'

  @videos = scopes.inject(Video) {|m,v| m.scopes[v[0]].call(m, v[1]) }.paginate(:all, :order => order, :page => params[:page])
end

I found this code soon after writing something similar for myself, and modifying his code to look like mine would give:

def index
  scope = Video
  scope = scope.by_user params[:user_id] if params[:user_id]
  scope = scope.tag_name params[:tag_name] if params[:tag_name]
  scope = scope.rating params[:rating] if params[:rating]

  order = { 'name' => 'videos.name ASC' }[params[:order]] || 'videos.id DESC'

  @videos = scope.paginate(:all, :order => order, :page => params[:page])
end

With this there’s no need for the inject/call, you just call the methods yourself, and skip building an array of symbols/strings. I’m sure this is more efficient, but does this make it more or less readable to you?

I saw Ryan Bates post a response to his article suggesting one could use his scope builder plugin which would turn the code into this:

def index
  scope = Video.scope_builder
  scope.by_user params[:user_id] if params[:user_id]
  scope.tag_name params[:tag_name] if params[:tag_name]
  scope.rating params[:rating] if params[:rating]

  order = { 'name' => 'videos.name ASC' }[params[:order]] || 'videos.id DESC'

  @videos = scope.paginate(:all, :order => order, :page => params[:page])
end

Seems like a very small gain for the introduction of a plugin. Am I missing something? Are there other examples where my solution would not be as attractive? Or is this a case where the original Rails code suits the purpose just fine?

Mock Expectation Fails on Associated Model (Association Proxy)

I just hit a real mean snag today trying to put an expectation on a model and it didn’t get caught like it should. This is something I do all the time so I knew I wasn’t just making a basic typo. Here’s what my setup looked like:

The model

def purchase
  send_shipping_notice
end
  
def send_shipping_notice
  puts "hi"
end

In my spec/test

def given_bought_on(date)
  wp = @order.product
  puts wp.inspect
  wp.should_receive(:send_shipping_notice)
  wp.purchase
end

The output

#<Product id: 270, owner_id: 270, name: 'thing'>
hi

1)
Spec::Mocks::MockExpectationError in 'Product should purchase'
Mock 'Product' expected :send_shipping_notice with (any args) once, but received it 0 times
./spec/models/product_spec.rb:21:in `given_bought_on'
script/spec:4:

So as you can see, I put an expectation on the wp variable, and verify it gets called by seeing ‘Hi’ get printed. However the expectation totally gets overlooked and my test framework complains that it never happened.

The catch is that the wp in my test and the web_page self inside the model are two different models. So the expectation is on a different model. Calling .object_id or .inspect on either one will affirm (or rather lie) to you that they’re the same, but they are not.

This is (as I’m told) well documented in ActiveRecord and is intentional by design. The wp variable is in fact an Association Proxy, not the WebPage I think it is. It just lies to you outright so you think it’s a WebPage.

My solution, while rather a hack, is the only thing I know to get my spec to pass:

def given_bought_on(date)
  wp = Product.find(@order.product_id)
  puts wp.inspect
  wp.should_receive(:send_shipping_notice)
  wp.purchase
  @order.reload
end

Now it grabs the model directly and there’s no confusion.

Rails Pagination by Letters (not Numbers like will_paginate)

I want my list of data to be paginated by letters. The will_paginate plugin certainly gives excellent pagination if all you want is “prev 1 2 3 .. 6 next” kind of pagination.

However what if you’re looking for entries that start with the letter H and you have no idea if that’s page 4 or page 42? You’re probably wanting something more like “# A B C D…” pagination.

I did some googling and found people speaking of solutions for “A B C D…” but in my case, not all of my entries start with letters! If you have something like media titles in your data set, having an entry start with a number is perfectly normal. Some might even start with special characters! Some people suggest having an ‘All’ option, but if you need pagination, it’s probably because you have enough data that showing all options at once is a very bad idea.

Here’s my solution:

First I make a helper function for my options that’ll be cached permanently.

def letter_options
  $letter_options_list ||= ['#'].concat(("A".."Z").to_a)
end

Here’s my index action in my controller:

@letter = params[:letter].blank? ? 'a' : params[:letter]
if params[:letter] == '#'
  @data = Model.find(:all, :conditions => ["title REGEXP ?", 
      "^[^a-z]"], :order => 'title', :select => "id, title")
else
  @data = Model.find(:all, :conditions => ["title LIKE ?", 
      "#{params[:letter]}%"], :order => 'title', :select => "id, title")
end

Here’s my html

<div class ="pagination">
  <% letter_options.each do |letter| %>
    <% if params[:letter] == letter %>
      <span class="current"><%= letter %></span>
    <% else %>
      <%= link_to letter, staff_games_path(:letter => letter)%>
    <% end %>
  <% end %>
</div>

There we go! Now the # will pull up all entries where the first character is not a letter.

Attachment Fu and Zip Files – RubyZip and Extracting For Bulk Uploads

Today I’m going to share how you can let your app accept zip files which will then be extracted into multiple attachments. If you already use Attachment Fu to manage files, this should be super easy to add.

Before my controller had something like this:

Attachment.create(:uploaded_data => params[:Filedata])

First break that up into something more like this:

attachment = Attachment.new
attachment.uploaded_data = params[:Filedata]
attachment.save

Then put in some split logic for if it’s a zip file:

attachment = Attachment.new
attachment.uploaded_data = params[:Filedata]
      
if attachment.content_type == "application/zip"
    # Unzip this and process all the inner files
    Attachment.build_from_zip(attachment)
else
    attachment.save
end

Now we’ll need the RubyZip gem and MIME Types gem, so do:

sudo gem install rubyzip
sudo gem install mime-types

Now in the Attachment class we’ll add the build_from_zip method:

def self.build_from_zip(ss)
 zipfile = Zip::ZipFile.open(ss.temp_path)
  
 zipfile.each do |entry|
   if entry.directory?
     next
   elsif entry.name.include?("/")
     next
   else
     screen = Attachment.new
     screen.filename = entry.name
     screen.temp_data = zipfile.read(entry.name)
      
     mime = MIME::Types.type_for(entry.name)[0]
     screen.content_type = mime.content_type unless mime.blank?

     screen.save
   end
 end
  
end

The only two non-obvious things to note here are:

  • I skip the files with / in them because when I archive files on my machine, hidden files with / characters in them make it into the archive. I only want the real files.
  • I use the MIME Types gem to decide the content type from the filename.

I hope you find this helpful, enjoy!

IE6 vs JavaScript (Prototype) and Sortable Elements Hidden/Disappearing/Invisible

Today I have another IE6 quirk in dealing with Prototype. I have a bunch of elements that I want to sort with a Sortable object, which works as expected for the most part, except in IE6!

I set background-color: #E6F2FF; to the rows of my ‘table’ which of course isn’t a real table, just some nicely styled div’s! The problem with this is that there’s a bug in this situation with IE6. All the contents of my rows are hidden! That means text, links, images. It looks as if all of the rows are just empty… except I can mouse-over and my mouse can interact with the elements.

It turns out, the opacity css attribute is 0. Or at least IE6 behaves as if it is. The thing is, FireBug doesn’t agree, and if you set opacity: 1; in your stylesheet it won’t cut it. The problem begins just after the Sortable.Create call. Removing this call fixes the problem, obviously, but that’s not what we want!

Here’s my solution. After the Sortable.Create call, do this:

if (Prototype.Browser.IE) {
    $$('.relevant_class_name').invoke.setStyle({opacity: 1});
}

where relevant_class_name is the css class assigned to the row’s of data on my page. This sets the opacity to all of the affected elements after the the Sortable object screws things up.

Hooray! I hope this helps someone else.

IE vs JavaScript (Prototype) and position: relative

In the current project I’m working on I have all sorts of cool AJAX madness happening to add things to my pages. For one particular example, I have AJAX calls that add pieces to the page with more AJAX functions inside of those pieces. Fun, huh?

The kicker is that some of the elements on the page there are being shown with the style attribute of position: static;. It just so happens that Prototype and position: static do not always play nicely in this case.

Let me show you what I mean. Here you see an image with two links placed with position: static;.

Clicking on “+ Add Foo” twice and “+ Change Bar” gives:

This works as expected. However if I add a duplication of this segment of the page using an AJAX call, the new portion does not work as expected while in Internet Explorer (every other browser works just fine of course).

To illustrate the problem while trying to do the same thing with the new segment, first I add the new segment via the “+ Add Many Things” link.

Then I click “- Change Bar” which hides the following two lines. You now see the two links I’ve been referring do not follow the movement of the page. They should have scrolled up and they did not.

To handle this, I need some extra JavaScript. First I add the following to the parameters of my AJAX call:

onSuccess: function(request){ reposition(); }

Next I want to take the elements that I know I have this problem with (selected by class name) and then first turn off the “relative” position by setting it to “static” and then back to “relative” again. By the way I figured this out thanks to way too much time fiddling with things in FireBug and the IE Dom Inspector. I can’t come up with any real sense to it. To make this happen, I add the following JavaScript for IE only:


function reposition() {
  if (Prototype.Browser.IE) {
     $('publication_list').select('.remove').each(swapStaticToRelative);
     $('publication_list').select('.add_company_role').each(swapStaticToRelative);
     $('publication_list').select('.add_cover').each(swapStaticToRelative);
  }
}
function swapStaticToRelative(item) {
	item.setStyle({ position: 'static' });
	item.setStyle({ position: 'relative' });
}

Note that the following also works for detecting that the browser is any version of IE:

function reposition() {
     /*@cc_on
          /*@if (@_win32)
               $('publication_list').select('.remove').each(swapStaticToRelative);
               $('publication_list').select('.add_company_role').each(swapStaticToRelative);
               $('publication_list').select('.add_cover').each(swapStaticToRelative);
          @else @*/
	      
          /*@end
     @*/
}

I wish I could remember where I found that, but it’s crazy cool. Sure the Prototype way is better… but this is a fun one to stare at for a while until you figure out how it’s working.

So with that change in place, this time it works as expected.

The only thing better I think (aside from IE not sucking) would be to have a way to select fields by style attributes. If I could figure that out, I’d be able to make a single call to swap these CSS attributes for all instances on the page that are relevant to this problem. If I could do that, then it might be a candidate for a patch to Prototype.

If you have a suggestion for a better way to handle this or if my post helped you out, please let me know by leaving a comment!

Easy PlainText Stories in Ruby on Rails using WebRat

Building off of my last post on Stories, I’d like to show you some improvements I was able to make with the help of WebRat.

You can install WebRat using:

script/plugin install http://svn.eastmedia.net/public/plugins/webrat/

I got the inspiration from Ben Mabey at ‘ben.send :blog’ which illustrates an approach to stories that is unbelievably easy to pick up. He also provides source code to the example that he’s built so that you can play with it. Very cool!

I snagged that source myself and started playing with it, and added a lot of functionality of my own that I found useful. The real cool thing is that the ONLY application specific code I have is this:

# user_steps.rb
steps_for(:user) do
  
  Given("that $actor is not logged in") do |actor|
    get "/sessions/destroy"
    response.should be_redirect
    flash[:notice].should == "You have been logged out."

    #fails here even though in the next request it will pass
    #controller.should_not be_logged_in
  end
  
  Then("$actor $should be logged in") do |actor, should|
    controller.send(shouldify(should), be_logged_in)
  end
  
end

# navigation_steps.rb
def path_from_descriptive_name(page_name)
  case page_name
  when "home"
    ""
  when "signup"
    "users/new"
  when "login"
    "sessions/new"
  else
    "#{page_name}"
  end
end

With that, and the generic application-independent helper methods that also live in my steps directory, I can write the following story:

Story: Making posts

  As a visitor
  I want to be a member
  So that I can make posts

    Scenario: Glenn creates a new account
      Given that Glenn is not logged in
      And there are some number of users
      And no user with login 'Glenn' exists
      
      When he goes to the signup page
      And fills in Login with 'Glenn'
      And fills in Email with 'glenn@glennfu.com'
      And fills in Password with 'blah'
      And fills in Confirm Password with 'blah'
      And clicks the 'Sign up' button

      Then there should be 1 more user
      And a user with login 'Glenn' should exist


    Scenario: Glenn logs in
      GivenScenario Glenn creates a new account

      When Glenn goes to the login page
      And fills in Login with 'Glenn'
      And fills in Password with 'blah'

      Then he should be logged in
    
    
    Scenario: A member makes a post while logged in
      GivenScenario Glenn logs in
      And there are some number of posts

      When he goes to the posts page
      And clicks on 'New post'
      And fills in Title with 'Woo'
      And fills in body with 'Stuff here'
      And clicks the 'Create' button

      Then there should be 1 more post
      And a post with title 'Woo' should exist


    Scenario: A nice lady makes a post while not logged in
      Given that she is not logged in
      Then she should not be logged in

      Given there are some number of posts

      When she goes to the posts page
      And clicks on 'New post'

      Then she should see the login page


    Scenario: An angry visitor should be able to see posts
      Given that he is not logged in

      When he goes to the posts page

      Then he better not f'ing see the login page


    Scenario: A visitor should not be able to edit posts
      GivenScenario A member makes a post while logged in
      And that she is not logged in

      When she goes to the posts page
      And clicks on 'Edit'

      Then she should see the login page

Pretty cool, huh? I was even able to add some personality with the angry visitor Scenario. Notice that I don’t have any steps written about Posts or my PostsController. I don’t need it because I have generic model steps that parse the model from the text in my PlainText instructions. It has all the basics that you need. You interact with forms and links on the top level by name, and then check the results either by text on the page or by finding models.

If you’d like to play with it yourself, I’ve zipped up my stories folder for you to play with.

Download it here!

It also has the old user_story.rb from the previous example. If you have any suggestions for improvements to this or ideas for something else I should do with it, please leave a comment!

My only quirk with this is that if you check the first step in the steps_for(:user) above you’ll see I have a line commented out. It checks for controller.should_not be_logged_in and if I uncomment it, the check fails even though the two above it pass. As you can see in the PlainText, on the very next line I call the very same check and it passes. If you could help me figure out my mistake here I’d really appreciate you leaving a comment with your thoughts!

I hope you find this useful, and if so, be sure to go toss a thank you over to Ben for the inspiration.