In Tealeaf Academy’s second course Rapid Prototyping with Ruby on Rails we built an app called Postit. It’s bassically a clone of the popular site Reddit.com. Of of the featues of this app is to add a URL to the site you are posting about. I decided to add a feature for grabbing an image on that URL. To do this I used some AJAX and a web scraping gem. Here is what the page looks like in action.
Here is the link to the actual working app http://shielded-retreat-9536.herokuapp.com/
Here is the code on GitHub https://github.com/doug7410/postit-app
You’ll have to register and create a post in order to try out the image feature.
Here the rundown on how I did it.
1. First add a column to the database for the image
The first thing I had to do was add a post_image
column to the posts
table. I went into the console and created a migration.
1
|
|
This created the migration file. Below is the file with the code to add the new column.
1 2 3 4 5 |
|
2. the basic workflow for getting the images from a URL
In this application a post has 5 attributes (not including timestamps). They are:
- title
- url
- description
- slug
- post_image
- user_id
I’m using a web scraper called metainspector to fetch an array of images from the url
given to the post. To access the images returned by metainspector I have to take following steps:
- create a link that sends an HTTP request to a
post_image
action in the post controller - that action renders a javascript template
- that template uses a method which is accessed on the post objecy
- inside that method, metainspector uses the
url
to return an array of images that are on the url’s web page - a list of images is shown and the user chooses an image for the post
That’s the basic outline of the workflow for getting the images. Here is how I did it.
3. The workflow in the UI
I’m using AJAX and a very slick JavaScript interface in the UI to select the image. The workflow in the UI works like this:
- when creating or editing a post, the user enters a url in the URL form field
- the user clicks on the “Choose Post Image” button
- underneath the URL field, a hidden div slides down with all the images generated from the URL
- the user clicks on an image to choose it and assign it to the post
Rails has a very easy way to AJAXify a link by adding remote: true
to a link_to
helper method. Here is the section of the form with the URL field and the link:
1 2 3 4 5 6 |
|
There are two things about this link use some special rails magic
remote" true
method: 'post'
remote: true
turns this into an AJAX link. It’s equivalent to writing a JavaScript event handler that process an AJAX call when the link is clicked.
method: 'post'
turns the link in to a POST request as opposed to a GET request.
Both of these are necessary for the next step, the controller action.
When the user adds a URL to the field and then clicks the “Choose Post Image” button it uses this rout:
1
|
|
The link goes to the post_image_path
, so a click on this link sends a POST request to the post
controller’s post_image
action , and because the link has the remote: true
parameter, the format is JavaScript.
4. what’s happening in the post_image
action in the post
controller
Here is the action in the controller:
1 2 3 4 5 6 7 8 9 |
|
Here there are 3 things happening.
- a new @post object is created. This is necessary because the post model has a method that fetches the images. So you need a post object to call that method
- a @url variable is created from the URL field in the form. If you look at the “Choose Post Image” link you probably notice there is no URL parameter anywhere. I used JavaScript to dynamically add the url as a parameter to the link.
- lastly, the action renders the default template
post_image.js.erb
. Since the request coming into the action is JavaScript formatted, therespond_to
block sends the request off to thepost_image.js.erb
template.
I should probably take a step back and show you the JavaScript that added the URL parameter to the link.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
NOTE: to get the code above to work properly I had to add the jquery-turbolinks gem to my gemfile
gem 'jquery-turbolinks'
Basically what this does is append ?url=
and whatever text is in the URL feild to the end of the link.
The link by default points to the /post_image
path. So if you type www.cnn.com
into the URL field, the link will automatically be turned into /post_image?www.cnn.com
5. inside the JavaScript template post_image.js.erb
After the response goes through the controller action it ends up in the JavaScript template. This is where all of the cool AJAX stuff happens. Here is what the template looks like.
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 |
|
To give you an idea of my though process I’ll try to explain what this file does.
- It creates an unordered list of images and then places them into a div with the ID of
#images_container
. - Once the image list is inside the
#images_container
, that div slides down. Keep in mind this is all happening on the create post or edit post page. - After the
#images_container
slides down, the user clicks on an image to choose it and assign it to the post. - once the chosen image has been clicked the
images_container
slides up and another hidden div,#post_image
gets the chosen image inserted into it and slides down. - there is also a hidden form field
#post_post_images
. The image URL is dynamically added to it’s value attriubte.
The important part of this code is @post.url_images(@url)
. Here is the url_images
method in the Post model
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
There are basically 3 cases that can happen in this method
- the URL passed in is an image URL
- the URL is a web page with images on it
- or there is a problem with the URL, in which case the string “error” is returned.
The first thing that happens is the URL is split apart on the dots (.) . If it’s an image URL like www.mysite.com/myimage.jpg
the first condition in the if statement will execute and an array with the URL string will be returned.
If that’s not the case the URL will be passed into MetaInspector and assigned to the page
variable. MetaInspector scrapes information from a given URL and gives you several methods for accessing that data. In this case I just want the images. page.images
returns an array of all the images retrieved by MetaInspector
If MetaInspector has a problem with the URL passed in it will throw an exception and the whole app will fail. To get around this I used a begin and rescue block
. This way if there are any exception thrown by MetaInspector the method will execute the rescue block and just return the string “error”.
So that’s the first thing going on the JavaScript template. The first line is
1
|
|
so as long as there is no error, this section will execute.
1 2 3 4 5 6 |
|
This creates tan unordered list of images and assigns the list to the images_html
variable.
That list is put into the #images_container
with this code
1
|
|
Then this part
1
|
|
slides down the #image_choices
and when an image is clicked 3 things happen
- this line
$("#post_post_image").val($(this).attr('src'));
adds the URL of the clicked image to a hidden form input"#post_post_image"
- the
#image_choices
div slides up - the image is added to the
#post_image
div and it slides down
1 2 3 4 5 6 7 8 9 10 |
|
6. finishing up the feature’s workflow
At this pont all that is left is the post has to be saved and the image URL will be added to the post_image
column. This is because of the hidden form field I talked about. Here is the hidden field in the form.
1
|
|
Also, just for clarification. Here is the entire post form.
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 34 |
|
I hope that made some sense and now you have some idea of how I did it. It took a combination of JavaScript, jQuery, AJAX, a scraper gem, and standard rails MVC. I’m sure there are things that can be improved on and tweaked so please feel free to leave feedback.
Thanks for reading!