Acad2552784135c09b17c00853f5a6f8

This is not my code but I want to see what can you do with it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def test_web_browser()
  if request.env["HTTP_USER_AGENT"][/Firefox\/3/]=="Firefox/3"
    return "Firefox3"
  else
    if request.env["HTTP_USER_AGENT"][/Firefox\/2/]=="Firefox/2"
      return "Firefox2"
    else
      if request.env["HTTP_USER_AGENT"][/MSIE 6/]=="MSIE 6"
        return "MSIE6"
      else
        if request.env["HTTP_USER_AGENT"][/MSIE 7/]=="MSIE 7"
          return "MSIE7"
        else
          if request.env["HTTP_USER_AGENT"][/Opera/]=="Opera"
            return "Opera"
          end
        end
      end
    end
  end
end

Refactorings

No refactoring yet !

8fb47582197b2d50cd7d297841c8cc87

chrismo

August 28, 2008, August 28, 2008 17:38, permalink

No rating. Login to rate!

Something like this would do I reckon - not necessarily very extensible if you need some real transformations done to future browse agent names.

1
2
3
def test_web_browser()
  return request.env['HTTP_USER_AGENT'].gsub(/\/| /, '')
end
A8d3f35baafdaea851914b17dae9e1fc

Adam

August 28, 2008, August 28, 2008 18:32, permalink

3 ratings. Login to rate!
1
2
3
4
5
6
7
8
def test_web_browser
  catch(:match) do
    ["Firefox/3", "Firefox/2", "MSIE 6", "MSIE 7", "Opera"].each do |ua|
      throw(:match, ua.gsub(/[^a-z0-9]/i, "")) if request.user_agent =~ Regexp.new(ua)
    end
    nil
  end
end
F288a8afe5302a16a366d5e9d34f2fec

Joe Grossberg

August 28, 2008, August 28, 2008 19:05, permalink

4 ratings. Login to rate!

A few additional points:

* it would be a lot cleaner to use if/elsif/elsif/.../end, rather than nesting such similar if statements
* return statements are almost always unnecessary in ruby
* predicate conditionals are a common idiom in Ruby and quite readable when you get used to it
* you can skip "else" statements when the conditions are mutually-exclusive

1
2
3
4
5
6
7
8
9
10
# fixed, per comment from N. Sanguinetti (below) -- thank you! :) Multiple return statements suck, though, and you should use his

def test_web_browser()
  ua = request.env["HTTP_USER_AGENT"]
  return "Firefox3" if ua[/Firefox\/3/]=="Firefox/3"
  return "Firefox2" if ua[/Firefox\/2/]=="Firefox/2"
  return "MSIE6" if ua[/MSIE 6/]=="MSIE 6"
  return "MSIE7" if ua[/MSIE 7/]=="MSIE 7"
  return "Opera" if ua[/Opera/]=="Opera"
end
E2eac01661fd490189a527b41c81a91c

Nicolás Sanguinetti

August 28, 2008, August 28, 2008 21:57, permalink

4 ratings. Login to rate!

Never. *Ever*, detect the user agent and act differently depending on the agent, unless you really understand why you shouldn't =)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Joe's solution doesn't work. It will always return either "Opera" or nil, you need to put "return" 
# at the beginning of each line. *This* is one of the cases where the return statement *is* required, 
# as you want to interrupt the flow of the code and return "early" instead of returning the return 
# value of the last statement in the method.

# Adam's solution is interesting, in that it's an abuse of throw/catch, when Enumerable provides with a perfectly valid method for *exactly* this use case:
def test_web_browser
  %w(Firefox/3 Firefox/2 MSIE\ 6 MSIE\ 7 Opera).detect do |agent|
    agent.gsub(/\W+/, "") if request.user_agent =~ Regexp.new(agent)
  end
end

# another solution, taking advantage of Regexp#=== and that case calls this method:
def test_web_browser
  case request.user_agent
  when /Firefox\/3/ then "Firefox3"
  when /Firefox\/2/ then "Firefox2"
  when /MSIE 6/ then "MSIE6"
  when /MSIE 7/ then "MSIE7"
  when /Opera/ then "Opera"
  end
end
A8d3f35baafdaea851914b17dae9e1fc

Adam

August 28, 2008, August 28, 2008 22:02, permalink

No rating. Login to rate!

@Nicolás - I don't think the detect method does what you think it does. You can do the following, but I decided I liked the other form better.

1
2
3
4
5
def test_web_browser
  %w(Firefox/3 Firefox/2 MSIE\ 6 MSIE\ 7 Opera).detect do |agent|
    request.user_agent =~ Regexp.new(agent)
  end.gsub(/\W+/, "")
end
E2eac01661fd490189a527b41c81a91c

foca

August 28, 2008, August 28, 2008 22:07, permalink

1 rating. Login to rate!

Er, sorry, I rushed to post and messed up the code, the idea was the one following.
(Also changing the array, it's much more clear to use a straightforward array than using %w())

1
2
3
4
5
def test_web_browser
  ["Firefox/3", "Firefox/2", "MSIE 6", "MSIE 7", "Opera"].detect do |agent|
    request.user_agent =~ Regexp.new(agent)
  end.gsub(/\W+/, "")
end
0f164d278a84fd814b8f7cb228dab565

nachokb.blogspot.com

August 28, 2008, August 28, 2008 22:25, permalink

2 ratings. Login to rate!

Nicolás' first solution is the best (you should have posted two solutions, as the second wouldn't have received a 5-star rating from me...), as it doesn't repeat a single concept; e.g. you want to add a new UA? just add it in the list of UAs! (notice how easy is to externalize it).

The only issue I find in this, is that I don't know if the original author intended to always respect the convention which says that the return identifier string should be the UA's name with non-word chars stripped (he may decide to call Safari "Apple's browser" for all that we know). So my solution is just Nicolás', but using a Hash instead of an Array.

All that being said, NEVER TRY TO DETECT THE USER AGENT, that's BAAAAAAAAAAAADD (tm).

Detect user's browser

1
2
3
4
5
6
7
8
9
10
11
def user_agent_name
  user_agents = {
    /Firefox\/3/ => "Firefox3",
    /Firefox\/2/ => "Firefox2",
    /MSIE 6/ => "MSIE6",
    /MSIE 7/ => "MSIE7",
    /MSIE 5/ => "you are the reason I hate my life",
    /Opera/  => "Opera"
  }
  user_agents.detect { |pattern,_| pattern === user_agent }[1] rescue nil
end
8fb47582197b2d50cd7d297841c8cc87

chrismo

August 29, 2008, August 29, 2008 01:42, permalink

No rating. Login to rate!

I submitted this earlier, but it didn't get approved for some reason - attempt #2. Naturally, this code presumes all future agent strings will follow the same pattern. But it's too short to not post.

1
2
3
def test_web_browser()
  request.env["HTTP_USER_AGENT"].gsub(/[^A-Za-z0-9]/, '')
end
8fb47582197b2d50cd7d297841c8cc87

chrismo

August 29, 2008, August 29, 2008 01:43, permalink

No rating. Login to rate!

Here's a more thorough version with a simple test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test_web_browser()
  return translate_user_agent(request.env["HTTP_USER_AGENT"])
end

def translate_user_agent(ua)
  ua.gsub(/[^A-Za-z0-9]/, '')
end

if __FILE__ == $0
  require 'test/unit'
  
  class TestCode < Test::Unit::TestCase
    def test_simple
      assert_equal('Firefox3', translate_user_agent('Firefox/3'))
    end
  end
end
8fb47582197b2d50cd7d297841c8cc87

chrismo

August 29, 2008, August 29, 2008 01:46, permalink

No rating. Login to rate!

Whoops - shorter regexp. I even re-read the dang section in Pickaxe looking for \W and read right past it.

1
2
3
def test_web_browser()
  request.env["HTTP_USER_AGENT"].gsub(/\W/, '')
end
E2eac01661fd490189a527b41c81a91c

foca

August 29, 2008, August 29, 2008 01:51, permalink

No rating. Login to rate!

Hm, good one. But what nachokb pointed out above, with his refactoring my array for a hash of "translations" makes sense tho.
Quick regexp thing: if you can't use \W for some reason (maybe periods and commas are allowed), you can still shorten your regexp by using /[a-z0-9]/i where the 'i' flag means case insensitive.

Acad2552784135c09b17c00853f5a6f8

dcadenas.blogspot.com

August 29, 2008, August 29, 2008 02:10, permalink

No rating. Login to rate!

@chrismo: check that your code would break with the tests I'm posting here, I think they show the original desired behaviour

Some changes to foca's idea:
1) make it more testable
2) make it more declarative with some variables and a method extraction so that I don't have to remember what \W+ does again (I have an awful memory!!)
3) change the method name

I also added some Rspec tests for the next refactorings so it's easy for people to know what's the desired behaviour and to know they are not breaking anything

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'rubygems'
require 'spec' 

def get_web_browser_id_string(user_agent = request.user_agent)
  web_browser_id_with_non_word_chars = ["Firefox/3", "Firefox/2", "MSIE 6", "MSIE 7", "Opera"].detect { |agent| user_agent =~ Regexp.new(agent)}
  remove_non_word_chars(web_browser_id_with_non_word_chars)
end

def remove_non_word_chars string_with_non_word_chars
  string_with_non_word_chars.gsub(/\W+/, "")
end

describe 'get_web_browser_id_string' do
  it 'should detect Firefox 3' do
    get_web_browser_id_string('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1').should == 'Firefox3'
  end

  it 'should detect Firefox 2' do
    get_web_browser_id_string('Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.13) Gecko/20080208 Mandriva/2.0.0.13-1mdv2008.1 (2008.1) Firefox/2.0.0.13').should == 'Firefox2'
  end

  it 'should detect MS Explorer 6' do
    get_web_browser_id_string('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; NeosBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)').should == 'MSIE6'
  end

  it 'should detect MS Explorer 7' do
    get_web_browser_id_string('Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; Zune 2.0)').should == 'MSIE7'
  end

  it 'should detect Opera' do
    get_web_browser_id_string('Opera/9.20 (Macintosh; Intel Mac OS X; U; en)').should == 'Opera'
  end
end
E2eac01661fd490189a527b41c81a91c

foca

August 29, 2008, August 29, 2008 02:23, permalink

No rating. Login to rate!

Why is that more testable?

I don't think extracting the gsub call into its own method makes any sense. If you have trouble remembering what \W means, either a) print a regexp cheatsheet or b) use /[^a-z0-9]/+

And BTW, you use the "m" flag, which I find even harder to remember than \w =P -- if it's the one that makes "." take into account linebreaks (and thus \W and all char classes, too), then it doesn't apply here anyway, since the user agent can't have linebreaks.

And if the answer is "because you'll use it later" in a string that needs to replace a \n then I say YAGNI. Don't refactor code that's not there yet. Don't over engineer it :)

8fb47582197b2d50cd7d297841c8cc87

chrismo

August 29, 2008, August 29, 2008 04:09, permalink

No rating. Login to rate!

well, that's what I get for being myopic...

Acad2552784135c09b17c00853f5a6f8

dcadenas.blogspot.com

August 29, 2008, August 29, 2008 17:19, permalink

No rating. Login to rate!

@foca: A method extraction purpose is not only reusing code, which I agree it would be YAGNI, in this particular case, and in most cases, I do it for clarity, to improve readability.

A child can understand what remove_non_word_chars means but it will take longer to understand what \W+ and /[^a-z0-9]/+ means, even if you are an expert in regexps. It gives more importance to the purpose and the thought process that occurred when writing this code than the implementation (using regexps).

The method is more testable because I neither need to set the request nor I need to mock it. It's just a string manipulation without a dependency to a request.

The m flag had no sense to be there, I was doing some experiments before posting and I forgot to remove it, but I removed it one minute after posting the code!!!! how did you see it?

E2eac01661fd490189a527b41c81a91c

foca

August 29, 2008, August 29, 2008 17:42, permalink

No rating. Login to rate!

@dcadenas: re: testability: oh, sorry, just saw how you passed in the request.user_agent as a default parameter, missed that on my first read =) And yeah, I agree that's more testable.

re: undestandability: I suppose it's a matter of how you look at it. If the *purpose* of the method is to ensure that all non-word characters are removed from the ua, then it might make sense to extract that to a method. If it's just a means to an end (getting an "identifier" for the UA), then adding a method will convolute things (you will see that and think that it's important because of the behavior, when it's just a consequence...)

In any case, returning a symbol would make more sense for this particular use case, instead of the different strings, so this might be a non issue after all (and then the best solution would be using the hash as nachokb proposed)

F55e8e113669f6ea7d1d99f38907ce54

seaofclouds

October 13, 2008, October 13, 2008 16:51, permalink

No rating. Login to rate!

i like what i see here, but there's no instructions for using these methods in a view. in my views, i'd like to do:
<% if browser(ff3) %>hurrah!<% elsif browser(ie6) %>bah!<% end %> can someone refactor the following to make this possible?

1
2
3
4
5
6
7
8
9
10
11
  def browser(b)?
    user_agents = {
      /Firefox\/3/ => "ff3",
      /Firefox\/2/ => "ff2",
      /MSIE 6/ => "ie6",
      /MSIE 7/ => "ie7",
      /MSIE 5/ => "ie5",
      /Opera/  => "opera"
    }
    request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"].include?(b)
  end
F55e8e113669f6ea7d1d99f38907ce54

seaofclouds

October 16, 2008, October 16, 2008 18:24, permalink

No rating. Login to rate!

here it is. to test firefox, use:

- if browser?(:ff)

in related refactorings, see http://refactormycode.com/codes/535-browser-detection-and-url-redirects-from-array

1
2
3
4
5
6
7
8
9
10
11
12
  def browser?(b)
    user_agents = {
      :ff   => /Firefox/,
      :iphone=> /(Mobile\/.+Safari)/,
      :safari=> /Safari/,
      :ie   => /MSIE/,
      :opera => /Opera/
    }
    user_agents[b] &&
      request.env["HTTP_USER_AGENT"] &&
      request.env["HTTP_USER_AGENT"] =~ user_agents[b]
  end

Your refactoring





Format Copy from initial code

or Cancel