kegunaan glucophage female uses of viagra cipro email address atarax use in babies doxycycline treatment dosage viagra with nitric oxide generic viagras cipal niacin compared to lipitor 1st cycle of clomid worked zoloft make me happy metformin effects on periods orlistat xenical nhs strattera and 5 htp buy drug valtrex how many mg of azithromycin should i take for chlamydia can clomid cause miscarriage pregnant nexium herpes what he best way to take viagra adalat sony tv september 25 metabolism of tamoxifen and cyp2d6 olanzapine solubility in water skopje viagra u skopje doxy antibiotics could eliminate back pain buy online how to take 1 pill of viagra plan b and diflucan zyprexa withdrawal insomnia zoloft cause weight gain or loss can u get high from zoloft when does viagra go generic viagra adderall with can you take diphenhydramine with prednisone xenical diary should i take topamax at night or in the morning can you buy chlamydia antibiotics over the counter esophageal damage in cats due to doxycycline buspirone imipramine pregnant after misoprostol miscarriage metronidazole dog not drinking water reviews of strattera abilify lexapro escitalopram vyvanse celexa together what happens when you take more than one cialis 5mg carbamazepine and gabapentin combination seroquel to sleep on adderall were can i get a viagra in south africa where can i get a cheap prescription for propecia diane 35 metformin pcos can i stop taking metoprolol orlistat anemia price of 50mg viagra cvs viagra from pfizer price karachi depression seroquel sertraline zoloft pregnancy lindeza orlistat y carnitina teva generic xenical erythromycin corneal abrasion orlistat overdose symptoms can zoloft lose effectiveness over time cialis is very expensive levitra kapseln why is prednisone given for only a short period of time keratosis pilaris and teva prednisone nizoral oral tablets diet with nexium amoxicillin while pregnant nhs buying viagra tj neurontin 400 mg street value does zoloft help your anxiety diflucan maschio finasteride bad for ulcerative colitis cialis night erections doxycycline accutane together will amoxicillin help a skin infection zithromax pediatrics intravenous mixing allegra aiuto does valtrex make birth control ineffective did you lose weight on xenical how does nexium work on gi tract caverject cialis mixing adderall and gabapentin interaction metformin crosses placenta plavix how long after stent tadalafil 20mg anwendung can an overdose of zoloft kill you viagra san antonio what is a cymbalta high like furosemide expiration date buspirone preparazione galenica hydrochlorothiazide and drinking water avodart tijuana cialis does not work now what clomid and weed cialis 4x20 acheter tadalafil 20mg where to buy amoxil in stores will cipro treat abscess what is the success rate of viagra amoxicillin nach verfallsdatum hydroxycut diet pills reviews best cialis deal apakah kegunaan viagra what is avodart taken for age need viagra does weed affect doxycycline uk clomid paypal found viagra his bag chris london viagra buy azithromycin tablets 250 mg morning sickness zofran constipation buspar ear pain how fast does bactrim ds work levitra rok trajanja sertraline 50 mg tabletas efectos nexium help with cost metallic taste in mouth cymbalta order liquid cialis lamisil svamp skridtet hard to orgasm when taking viagra thyroxine libre taux super levitra force cuanto valen las pastillas de cytotec en colombia finasteride e donazione sangue ciprofloxacin 500 normal prednisone dosage for allergic reaction zoloft and wikipedia fluconazole dosing toenail fungus clomid polska apteka cialis online apteekki.eesti visa electron comprimido semelhante ao viagra were to buy clomiphene for men and women online furosemide 20 mg ampule real generic cialis and levetra viagra yellow triangle what vitamins should i take with synthroid can i take pepto bismol while on prednisone hydrochloric doxycycline heartburn feline cyproheptadine side effects allegra danza milano para que sirve el antibiotico augmentin propranolol for liver hemangiomas generic topamax for migraines downside of celexa mixing prednisone and naproxen slang for viagra ciprofloxacin dosage dogs propecia ever cause hair loss is lexapro a statin metoprolol and low pulse how to buy propecia australia cialis sample dosage 60 mg levitra viagra plus viagra super active how do i buy clomid where to buy clomid nz strattera drug use paroxetine usp monograph klada av inderal can i take cialis and citalopram adalat serial actress clopidogrel thera farm is wellbutrin an anti anxiety drug how to sell viagra on line what is a drug called metronidazole vasodilator erection viagra online shipped monthly generic zofran while pregnant how long should i take amoxicillin for chlamydia what is allegra d prescribed for another name for clopidogrel nursing implication for olanzapine valtrex in india can lipitor reverse plaque build up plavix na dotteren cialis target pharmacy plavix ratings baclofen hyponatremia coming off lexapro 5mg over the counter ondansetron proscar and liver kroger cialis cost generic viagra ranbaxy flagyl 400 mg dosagem glucophage carb up albuterol 3d benadryl and acyclovir pastilla del dia siguiente cytotec what are the side effects for furosemide paxil testicle pain synthroid and vegan diet levitra aumenta libido wellbutrin sr weight loss prednisone may mask nexium dosage 40 mg brandon marshall nfl viagra sildenafil citrato masticable buy propecia edmonton cymbalta sedative prescription zantac vs nexium doxycycline dosage guidelines misoprostol hemorragia uterina hydromorphone and celexa clomid man lisinopril prices albuterol sulfate 0.083 nebulizer solution shelf life seroquel celexa together clomid and high fsh level pharmacy direct gb propecia nolvadex na noc doxycycline 100 mg price costco hfmd zovirax is zithromax effective against e coli tongkat ali finasteride what does ciproleex tz msc viagra urologe wien viagra buy cialis cvs quero saber tudo sobre viagra thyroxine dosage does medco have viagra how much weight will i lose taking orlistat savella drug interactions cymbalta generic viagra online fast can clomid cause cysts on ovaries fluconazole teva 150 mg prospect zithromax 500 mg dosage augmentin 500mg tablets teratogenic effects of bupropion people who take gabapentin 100mg beta blockers propranolol 80 mg migraine nexium lawsuit settlement prednisone and zinc can i take singulair if i am pregnant swollen feet cialis wellbutrin dht maximum dosage seroquel adults will cialis split between man and woman celecoxib drug study nursing responsibilities zyban generic bupropion key proscar west emergence of tadalafil 5 mg in pah what type of depression is wellbutrin for does clomid thin your lining viagra import australia administrarea viagra gestopt met zoloft is paroxetine hcl the same as paroxetine valtrex pricing walmart can you get pregnant while taking ciprofloxacin vardenafil generic brand levitra 20mg apotheke wellbutrin and cancer promethazine and paxil benefits of prednisone for arthritis viagra offer mail gebelikte metformin kullanimi onde comprar o remedio xenical prednisone c est quoi allegra 6r beckman drug interactions azithromycin tylenol synthroid keppra buy nolvadex post cycle therapy australia phenergan wholesale allegra somerton rd mist? viagra resepti neurontin medical bracelet lipodystrophy prednisone lipitor hair loss price cialis vs viagra levitra in usa kaufen pms quetiapine for sleep levaquin every other day was generic lipitor recalled zyprexa side effects slurred speech viagra fake how to spot can you mix viagra and weed right dosage strattera buy viagra kazakhstan efectos del magnus sildenafil metronidazole gel liver despues de cuantas horas hace efecto el cytotec famciclovir en mexico antidepressivi e viagra kamagra and amlodipine used together nizoral hair loss steroids can nexium give me black eyes msd maxalt melt cipro patent expiration date daptomycin linezolid antagonism viagra fro bangkok zofran without insurance how long does lamisil stay in your system cipro dose in hemodialysis efectos secundarios de pastillas cytotec what will happen if i stop taking zoloft amoxicillin antibiotic shelf life cymbalta opiophile dosering av cialis precio allegra 180 colombia esomeprazole better sandoz topiramate 25 mg prezzo del viagra in italien can cytotec be used in 4months pregnancy allegra hospital shoes propecia coat can i donate blood on valtrex cialis not working like it used to nexium cause gastroparesis viagra cars maxalto crono what is thyroxine made up of nizoral hard to find 2013 celexa imipramine is cialis online safe metronidazole side effects blurred vision nebenwirkungen nexium mups 20 eugene shippen propecia how long does ic bupropion hcl xl last zoloft 100 mg filagra vs viagra advil and cipro interaction eventually your hair will fall out on propecia lexapro muscle tension cipro eye drops in pregnancy vipro lifescience tadalafil review lipitor side effects bruising miglior sito dove acquistare viagra difference between cialis and viagra uk manfaat diflucan flconazole zoloft interaction viagra 50 mg pfizer prix zovirax creme baby nagle odstawienie lexapro gabapentin paxil overuse of viagra among young craigs list for cialis orlistat hexal forum how to get viagra as sample what dose azithromycin for 4 year old common retail prices cialis que precio tiene la pastilla cytotec en venezuela amoxicillin mixed with vicodin efek samping obat vibramycin 100 gabapentin drug to drug interactions prefer cialis 10 mg or 20 mg dose of famvir for cold sores doxycycline for cat renal failure can propecia make your hair fall out faster argo table maxalto acyclovir 400mg tablets australia famciclovir how supplied zovirax jarabe para varicela zyprexa uppdrag granskning bootleg viagra for sale metronidazole urine discoloration can you take voltaren 50mg and thyroxine prednisone dosing schedule for back pain atomoxetine attention tomoxetin discount viagra cialis honolulu hawaii can u get high off allegra usual dosage levaquin can thyroxine affect blood pressure zosyn to augmentin conversion how long for lexapro to begin working alprazolam metronidazole giardiasis treatment metronidazole how long will i have a hard on after taking cialis seroquel good for bipolar lowest price sildenafil 100 mg counceling for erectile dysfunction how to reduce wellbutrin dosage albuterol sulfate 2.5 mg for weight loss para que sirve el gabapentin 100mg patient review escitalopram premature ejaculation take garlic with viagra al cuanto tiempo hace efecto el viagra finasteride 5mg how to cut it 8 doxycycline 100mg cap wot r they used 4 doxycycline efracea cialis dc before surgery can you take 40 milligrams of cialis premarin heart attack how do people take wellbutrin recreationally nursing interventon for a patient getting furosemide viagra effects on young people clomid lean pcos acheter cialis pharmacie en ligne buy clomid bank account how much nolvadex post cycle tranny fuck with viagra should i take 10mg or stay on 5mg with lexapro levitra receteli mi zithromax 500mg 3 can prednisone cause leukocytosis propecia impair fertility neurontin blood tests wellbutrin moclobemide premarin cream vulvodynia nexium in pediatrics thuoc fluconazole cap does ciprofloxacin treat trichomoniasis diflucan si alcoolul chantix vs. wellbutrin for smoking cessation how best to use clomid role of montelukast in urticaria vardenafil hcl cas cipro for vibrio zoloft handout vibramycin online how many days to take azithromycin import viagra to canada much amoxicillin 500mg should take strattera price australia famvir ibuprofen clopidogrel nach stemi cuanto cuesta cialis en colombia nifedipine mechanism of action alli synthroid interaction on peut tomber enceinte avec clomid can baclofen cause kidney problems olanzapine and zoloft cipropil 500mg tab tetracycline gene induction prednisone show drug test can clomid cause hair loss after pct amoxicillin dosage for urinary tract infection splitting lexapro dosages viagra and caverject together cialis double dosage acyclovir cream mercury drug philippines permanent effects of celexa allegra loja de sapatos big white pill for erectile son efectivas las pastillas cytotec para abortar origine e significato del nome allegra ciprofloxacin dose for traveler diarrhea how long is prednisone shelf life doxycycline cured my perioral dermatitis nexium 20 mg prospecto touched by allegra skye baclofen discontinuation paxil cr for pmdd augmentin and prilosec orlistat ritirato in francia doxycycline acne org review informacion de la cialis thrush prednisone side effects brand name ladies viagra thyroid thyroxine cialis information francais gabapentin initalian gabapentin essential tremor topamax bladder infection can you take amoxicillin and birth control how long does azithromycin take to cure pneumonia fun things to do on gabapentin viagra available over counter mexico what drug classification is inderal can lamisil spray work for anal yeast infection cobix generic celebrex pharmacy prednisone stomach swelling how does zovirax works monitoraggio follicolare dopo clomid man take viagra tsa pension chasa allegra sildenafil makes guys last longer viagra how much harder atorvastatin and hip pain wellbutrin increase energy what happens when you mix alcohol and wellbutrin ir wellbutrin valerian and zoloft can lipitor cause low potassium what can i do to make clomid work better prostate cancer and erectile dysfunction xenical side effects otc what is better for cold sores abreva or zovirax cipro deregistration of cc dapoxetine hcl manufacturer does cymbalta cause low blood sugar reason for erythromycin in newborns prostate biopsy on plavix sospensione finasteride effetti collaterali zithromax rxlist azithromycin for babies 2013 time to take viagra best what clomid looks like alcohol while on ciprofloxacin acyclovir denk cream used cialis super cheap bactrim ds skin rash amoxicillin making baby sick zoloft e infertilit? maschile cipro 500mg manufacturer amoxicillin n2 lasix 500 mg nereden alabilirim dilantin viagra blueberry erectile dysfunction metronidazole pills otc maxalt patient assistance lost weight after stopping celexa 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.