Google Map View For Rails App
Published on Apr 06, 2011
I recently built a view for a dynamic google map on a Rails application. Displaying the map then is as simple as dropping an iframe tag with parameters for latitude and longitude in the view. For this post I will be creating a personalized dynamic map for users.
When a user creates an account the app relies on the geokit-rails3 gem to geocode the user’s location and store the latitude and longitude in the database.
1 class User < ActiveRecord::Base
2 before_validation :geocode_address
3
4 def geocode_address
5 geo = Geokit::Geocoders::MultiGeocoder.geocode(self.location)
6 errors.add(:address, "Could not Geocode address") if !geo.success
7 self.lat, self.lng, self.zip = geo.lat,geo.lng,geo.zip if geo.success
8 end
9 end
In the maps controller the index action is set to pass the params for latitude and longitude. Also the layout is set to false, so the application layout won’t show up in the iframe.
1 class MapsController < ApplicationController
2 layout false
3 def index
4 @lat = params[:lat]
5 @lng = params[:lng]
6 end
7 end
In order to really understand the code on the map index view reading the google maps JavaScript API docs it’s a must.
1 <head>
2 <style type="text/css">
3 html { height: 100% }
4 body { height: 100%; margin: 0px; padding: 0px }
5 #map_canvas { height: 100% }
6 </style>
7 <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
8 <script type="text/javascript">
9 var city = new google.maps.LatLng(<%= @lat -%>, <%= @lng -%> );
10 var place = new google.maps.LatLng(<%= @lat -%>, <%= @lng -%> );
11 var marker;
12 var map;
13 function initialize() {
14 var mapOptions = {
15 zoom: 15,
16 mapTypeId: google.maps.MapTypeId.ROADMAP,
17 center: city};
18 map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
19 marker = new google.maps.Marker({
20 map:map,
21 draggable:true,
22 position: place
23 });}
24 }
25 </script>
26 </head>
27 <body onload="initialize()">
28 <div id="map_canvas"></div>
29 </body>
Once the map index view is ready, it can be placed in other views with the code below.
1 <iframe src="/map/?lat=<%= @user.lat %>&lng=<%= @user.lng %>&zoom=15" width="400" height="400" frameborder="0"></iframe>
Rails - Tagging a Model.
Published on Mar 12, 2011
Tagging is a very simple concept. Wikipedia describes a tag as “a non-hierarchical keyword or term assigned to a piece of information”.
There are few gems out there about tagging such as acts-as-taggable-on, is_taggable and so on. I do value the convenience and power of gems. And if I can I’d rather build my own logic. Mostly because it is the easiest way to deepen my knowledge on the subject.
If you are dealing only with one model that needs tagging, developing such a system in Rails is quite simple. For this article I will use tagging a blog post as example.
I usually establish the model associations right away. In this case it’s a many to many association between tags and posts like so:
In the post model:
1 class Post < ActiveRecord::Base
2 has_many :tags, :through => :posts_tags
3 has_many :posts_tags
4 end
In the tag model:
1 class Tag < ActiveRecord::Base
2 has_many :posts_tags
3 end
In the join post tag model:
1 class PostsTag < ActiveRecord::Base
2 belongs_to :post belongs_to :tag
3 end
Assuming that your post table is already in place, the next step is to generate the migrations to create the tags and the join posts_tags tables.
$ rails g migration CreateTags
$ rails g migration CreatePostsTags
1 class CreateTags < ActiveRecord::Migration
2 def self.up
3 create_table :tags do |t|
4 t.string :name
5 t.timestamps
6 end
7 end
8
9 def self.down
10 drop_table :tags
11 end
12 end
1 class CreatePostsTags < ActiveRecord::Migration
2 def self.up
3 create_table :posts_tags do |t|
4 t.references :post
5 t.references :tag
6 t.timestamps
7 end
8 end
9
10 def self.down
11 drop_table :posts_tags
12 end
13 end
Let’s not forget to migrate.
$ rake db:migrate
After the migration, controller’s actions are next so that we can find, create and associate tags to posts when posts are created or edited.
1 class PostsController < ApplicationController
2 def new
3 @post = Post.new
4 @tags = Tag.all
5
6 respond_to do |format|
7 format.html # new.html.erb
8 format.xml { render :xml => @post }
9 end
10 end
11
12 def edit
13 @post = Post.find(params[:id])
14 @tags = Tag.all
15 end
16
17 def create
18 @post = Post.new(params[:post])
19 unless params[:tags].nil?
20 params[:tags] = params[:tags].collect{|tag| Tag.find_or_create_by_name(tag)}
21 end
22
23 respond_to do |format|
24 if @post.save
25 @post.create_new_tags(params[:new_tags][0]) unless params[:new_tags].nil?
26 @post.tags << params[:tags] unless params[:tags]nil?
27
28 format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
29 format.xml { render :xml => @post, :status => :created, :location => @post }
30 else
31 format.html { render :action => "new" }
32 format.xml { render :xml => @post.errors, :status => :unprocessable_entity }
33 end
34 end
35 end
36
37 def update
38 @post = Post.find(params[:id])
39 unless params[:tags].nil?
40 params[:tags] = params[:tags].collect{|tag| Tag.find_or_create_by_name(tag)}
41 end
42 respond_to do |format|
43 if @post.update_attributes(params[:post])
44 @post.create_new_tags(params[:new_tags][0]) unless params[:new_tags].nil?
45 @post.tags << params[:tags] unless params[:tags].nil?
46 format.html { redirect_to(vendors_path, :notice => "Vendor was successfully updated.") }
47 format.xml { head :ok }
48 else format.html { render :action => "edit" }
49 format.xml { render :xml => @vendor.errors, :status => :unprocessable_entity }
50 end
51 end
52 end
53 end
On line 25 and 45 a “create_new_tags(params[:new_tags])” method is called for @post. This method allows the user the option to create new tags by providing a list of comma separated values. The method in the post model looks like this:
1 class Post < ActiveRecord::Base
2 def create_new_tags(tags)
3 new_tags = tags.split(%r{,\s*})
4 new_tags.each do |tag|
5 self.tags.create(:name => tag)
6 end
7 end
8 end
Finally the post form view brings the above logic to the surface. The text field starting on line 1 is add new tags as a list of comma separated values. The check boxes assign existing tags to the post.
1 <div>
2 <%= label_tag :tags %>
3 <%= text_field_tag('new_tags[]')%>
4 </div>
5 <div>
6 <% @tags.each do |tag| %>
7 </div>
8 <%= check_box_tag('tags[]', tag.name) %>
9 <%= label_tag(tag.name)%>
10 <% end %>
And that’s it. You can now tag the post model.
Using UTC Offset in a Rails app.
Published on Feb 11, 2011
Time zone math is no pleasure cruise, especially if, like me, you are new to the matter. Having users scattered all around the globe, apps need to be intelligent enough to be aware of the correct time for each user - particularly when there are scheduled events and notifications that need to be triggered at a specific time for a specific user.
I pretty much exhausted the internet recently looking for articles about it, and I certainly could have used some more good ones.
The UTC* offset is without a doubt the one indispensable thing when dealing with time zone calculations, and more specifically the UTC offset expressed in hours. For those not familiar with the concept, the UTC offset is the time offset of a specific time zone from the UTC. In my case I first derived the latitude and longitude from the user’s address using the geokit-rails3 gem, then I used earthtools.org to get the utc_offset. I then saved it in the user table.
In the user model:
1 class User < ActiveRecord::Base
2 def utc_offset
3 location = "#{self.address}, #{self.city} #{self.state} #{self.zip}"
4 geo = Geokit::Geocoders::MultiGeocoder.geocode(location)
5 url = "http://www.earthtools.org/timezone/#{geo.lat}/#{geo.lng}"
6 response = Array(HTTParty.get(url).parsed_response)[0][1]
7 offset = Array(response)[9][1]
8 self.update_attributes(:utc_offset => offset)
9 end
10 end
I picked Earthtools.org because it’s free, quick and dirty, and I was really eager to dive into the somewhat frustrating subject that is time zone math.
Rails stores all dates in the database in UTC, so that really helps. Also to avoid any other problems I also set up my app default time to UTC.
In the config/application.rb:
config.time_zone = 'UTC'
Finally, since we are using the DateTime new_offset method to “localize” the date/time needed for our actions, it’s good practice also to store the dates and time in the database as datetime data type.
The “localization” happens like so:
1 t = User.find(n).date
2 offset = Rational(user.utc_offset,24)
3 localize_datetime = t.new_offset(offset)
The new_offset method creates a new DateTime object, identical to the current one, except with a new time zone offset (where offset is the new offset from UTC as a fraction of a day). That is why I used Rational to convert the offset into a fraction.
*UTC (Coordinated Universal Time), differently from GMT (Greenwich Mean Time), is based on atomic time and includes leap seconds as they are added to our clock every so often.Wait, What's the Purpose Here Again?
Published on Jan 06, 2011
At some point in the summer of 2010 I decided to replace my static website, aimed at marketing my skills as a HTML/CSS/PHP developer and designer, with a blog about my new endeavor: web development in the Ruby world (mostly focused on Rails at the moment). I figured that one of the ways for me to learn more about programming was to write posts about the technologies and the challenges I encounter in my everyday work, therefore the blog.
Picking up the technology behind it was quite exciting. There are plenty of blog solutions out there, but I really wanted to avoid using a clumsy CMS, and I wanted to be simple. Fortunately Tom Preston-Werner’s jekyll, a very simple blog aware static site generator, came to the rescue. Once I had jekyll in place all I needed was a design and some content.
I’ve never had a blog before – I even have a hard time keeping a travel journal (which, according to me, is one of the best things ever) – so my biggest concern was writing the content. Also I didn’t mention that I was born and raised in Italy, and, as is the case for most of the folks from the Italian peninsula, my first language is Italian. So writing in English, even if not a challenge, doesn’t come naturally to me. I quickly found out that content was not scarce. Working as a Ruby and RoR apprentice, everyday I come across perfect topics for articles. All I need to do is take some time to put down reflexions and thoughts.
Riding the wave of excitement, I decided that if I were to start a blog, it should look really good. But design is not necessarily my forte and I wasn’t going to hire somebody to do it. So I rolled up my sleeves and started playing with some CSS3 gradients, shadows, feature and so on. Nothing really felt right and I was losing track of my real goal: learning more by producing content.
On January 5th, 2011 I decided that it was time for me to move on with the project and make something happen. I don’t believe in New Year’s resolutions (I believe in resolutions all the time), but the new year started with the idea of making a doer of myself. I have ideas all the time, I start things all the time, but sometimes I get stuck seeking perfection. I was looking for the perfect design, and it wasn’t anywhere to be found, so I stopped looking for it. Now my blog is far from pretty, but it’s functional, and that was all that I needed in the first place.
Ultimately life to me is about the experience; having a beautiful site design is not going to add anything to my experience of writing and learning, so why should I concern myself with it, if it doesn’t come easily? I know that as I go forth with this project things will come up, designs will come to mind, and they will be shaped around the experience that the content already provides rather than the other way around.