28 Mar Consuming Tumblr blog from your website using Spree
Consuming Tumblr blog from your website using Ruby on Rails (Spree), Backbone and Tumblr Api
You need to have an account on tumblr of course and generate the application to be able to consume the tumblr api:
http://www.tumblr.com/oauth/apps
About the default callback you can choose any url for example: http:://localhost:3000
Then generate the application oauth in the following url:
https://api.tumblr.com/console/calls/user/info
Add the following gem to your Gemfile:
gem 'tumblr_client'
Now from console you can do some tests before starting working with the project files:
token_params = {
consumer_key: 'some test',
consumer_secret: 'some test',
oauth_token: 'token',
oauth_token_secret: 'oauthtoken'
}
client = Tumblr::Client.new(token_params)
client.info
client.posts("your-tumblr-url.tumblr.com", limit: 10, offset: 2)
And now let’s start working with our project files
Gemfile
gem 'tumblr_client'
app/assets/javascripts/my_project.js.coffee:
window.MyProject =
Models: {}
Collections: {}
Views: {}
Routers: {}
Router: null
PostPerPage: 8
OrderNumber: null
initialize: ->
@Router = new MyProject.Routers.MainRouter()
Backbone.history.start()
@
MyProject.InitializeBlog = ->
blog = new MyProject.Models.Blog()
blog.fetch
success: (model) ->
collection = new MyProject.Collections.PostsCollection(
blog.get('posts')
)
view = new MyProject.Views.BlogView
model: blog
collection: collection
el: "#blog-content"
currentPage: 1
view.render()
error: ->
$("#loading").html 'The tumblr blog is not available.'
$(document).ready ->
MyProject.initialize()
app/assets/javascripts/helpers/post_types.js.coffee:
Handlebars.registerHelper 'PostType', (post)->
post_date = post.date.replace(' GMT', '')
postDate = $.format.date(post_date, 'MMM dd,yyyy')
linkURL = post.post_url
htmlContent = "<div class='post #{post.type}-post'>"
switch post.type
when "audio"
audioTitle = "AUDIO:"
imgSRC = (if post.album_art then "<img src=' #{post.album_art} '/>" else " ")
htmlContent += "<h2> #{audioTitle} </h2><p class='post-date'> #{postDate} </p> #{imgSRC} #{post.player} #{post.caption} <a href=' #{linkURL} '>Go to tumblr post...</a>"
when "text"
htmlContent += "<h2> #{post.title} </h2> <p class='post-date'> #{postDate} </p> #{post.body} <a href=' #{linkURL} '>Go to tumblr post...</a>"
when "photo"
photos = post.photos
if photos.length > 1
htmlContent = "<div class='post photo-post multi-photo'>"
else
htmlContent = "<div class='post photo-post'>"
for photo in photos
photoSizeURL = (size for size in photo.alt_sizes when 400 <= size.width <= 500)[0]
photoSizeURL ||= photo.original_size
htmlContent += "<a href=' #{photo.original_size.url} ' target='_blank' title=' #{photo.caption} '><img src=' #{photoSizeURL.url} '/></a>"
unless photo.caption is ""
htmlContent += "<figcaption> #{photo.caption} </figcaption>"
htmlContent += "<p class='post-date'> #{postDate} </p> #{post.caption} <a href=' #{linkURL} '>Go to tumblr post...</a></div>"
when "quote"
htmlContent += "<p class='post-date'> #{postDate} </p><q class='quote-text'> #{post.text} </q><p class='quote-author'> — #{post.source} </p><a href=' #{linkURL} '>Go to tumblr post...</a>"
when "video"
htmlContent += "<p class='post-date'> #{postDate} </p>"
htmlContent += "#{post.player[2].embed_code} #{post.caption} <a href=' #{linkURL} '>Go to tumblr post...</a></div>"
when "link"
description = post.description or ""
htmlContent += "<p class='post-date'> #{postDate} </p> <a href=' #{post.url} '> #{post.title} </a> #{description} <a href=' #{linkURL} '>Go to tumblr post...</a>"
when "chat"
htmlContent += "<p class='post-date'> #{postDate} </p>"
for dialogue in post.dialogue
htmlContent += "<span class='chat-post-name'> #{dialogue.name} </span><p class='chat-post-phrase'> #{dialogue.phrase} </p>"
htmlContent += "<a href=' #{linkURL} '>Go to tumblr post...</a>"
return new Handlebars.SafeString(htmlContent)
app/assets/javascripts/collections/posts_collection.js.coffee:
class MyProject.Collections.PostsCollection extends Backbone.Collection url: 'api/blog'
app/assets/javascripts/models/blog.js.coffee:
class MyProject.Models.Blog extends Backbone.Model urlRoot: 'api/blog'
app/assets/javascripts/models/post.js.coffee:
class MyProject.Models.Post extends Backbone.Model urlRoot: 'api/blog'
app/assets/javascripts/views/blog/post_view.js.coffee:
class MyProject.Views.PostView extends Backbone.View
template: HandlebarsTemplates['blog/post_view']
tagName: 'article'
render: ->
$(@el).html(@template( @model.toJSON() )).fadeIn()
@
app/assets/javascripts/views/blog/blog_view.js.coffee:
class MyProject.Views.BlogView extends Backbone.View
template: HandlebarsTemplates['blog/blog_view']
initialize: (options) ->
@collection.on 'reset', @addAllPosts, @
@currentPage = options.currentPage
@totalPosts = parseInt(@model.get('total_posts'))
$(window).bind "scroll", (ev) =>
@checkScroll()
render: ->
$(@el).html(@template( @model.toJSON() ))
@addAllPosts()
@
addAllPosts: ->
@collection.each @addPost, @
checkScroll: ->
isLastPage = @isTheLastPage()
@removeLoadingResults()
windowValue = ($(window).innerHeight() + $(window).scrollTop() + 400)
@getMorePosts() if windowValue >= $("body").height() and not isLastPage
getMorePosts: ->
next_page = (@currentPage + 1) * MyProject.PostPerPage
@model.fetch
data:
page: next_page
success: (model) =>
@currentPage = @currentPage + 1
@collection.reset(@model.get('posts'))
error: ->
alert "a ocurrido un error favor de intentarlo mas tarde..."
async: false
removeLoadingResults: ->
page = (@currentPage + 1) * MyProject.PostPerPage
if @totalPosts <= page @$el.siblings('#loading').remove() isTheLastPage: ->
currentPage = @currentPage * MyProject.PostPerPage
@totalPosts <= currentPage addPost: (post) ->
currentView = new MyProject.Views.PostView
model: post
@$("#posts-list").append currentView.render().el
app/assets/templates/blog/blog_view.hbs.haml
%h1 Hello from the content blog %hr #posts-list
app/assets/templates/blog/post_view.hbs.haml:
{{PostType this}}
%hr
app/controllers/spree/blog_controller.rb:
module Spree
class BlogController < Spree::StoreController
def index
end
end
end
app/controllers/spree/blog_entries_controller.rb:
module Spree
class BlogEntriesController < Spree::StoreController def index blog = ENV['TUMBLR_BLOG_URL'] posts = TumblrApiService.get_posts page_params, blog if posts['total_posts'] > 0
render json: posts, status: :ok
else
render json: [], status: :unprocessable_entity
end
end
private
def page_params
(params[:page].to_i || 1 ) - 1
end
end
end
app/services/tumblr_api_service.rb:
class TumblrApiService
def self.get_posts page, blog
client = Tumblr::Client.new
client.posts blog, limit:8, offset: page
end
end
config/initializers/tumblr.rb:
Tumblr.configure do |config| config.consumer_key = ENV['CONSUMER_KEY'] config.consumer_secret = ENV['CONSUMER_SECRET'] config.oauth_token = ENV['ACCESS_TOKEN'] config.oauth_token_secret = ENV['ACCESS_TOKEN_SECRET'] end
config/routes.rb:
get 'blog' => 'spree/blog#index' get 'api/blog' => 'spree/blog_entries#index'
vendor/assets/javascripts/spree/frontend/all.js:
//= require_tree ../../lib
app/views/spree/blog/index.html.haml:
%h1
Welcome to the blog
#blog-content
#loading
= image_tag 'loading.gif'
:javascript
$(function() {
MyProject.InitializeBlog()
});
Remember to add a loading image in:
app/assets/images/loading.gif
test suite:
Gemfile
gem 'vcr' gem 'webmock'
features/support/webmock.rb:
require 'webmock/cucumber' WebMock.disable_net_connect!(:allow_localhost => true)
spec/controllers/spree/blog_entries_controller_spec.rb:
require 'spec_helper'
describe Spree::BlogEntriesController do
describe '#index' do
context "when the query is successfull" do
specify do
VCR.use_cassette 'tumblr_api_controller' do
get :index
expect(response).to be_success
expect(JSON(response.body)['blog']['name']).to eq 'yourblogname'
expect(JSON(response.body)['posts'].count).to eq 7
end
end
end
context "when the query fails" do
let(:posts) do
{
'total_posts' => 0
}
end
before do
TumblrApiService.should_receive(:get_posts)
.and_return(posts)
end
specify do
get :index
expect(response.status).to eq(422)
expect(response.body).to eq('[]')
end
end
end
end
spec/services/tumblr_api_service_spec.rb:
require 'spec_helper'
describe TumblrApiService do
describe '.get_posts' do
let(:page){ 1 }
let(:blog_name){ ENV['TUMBLR_BLOG_URL'] }
specify do
VCR.use_cassette 'tumblr_api' do
result = TumblrApiService.get_posts page, blog_name
expect(result['blog']['name']).to eq 'yourblogname'
expect(result['blog']['posts']).to eq 2
expect(result['posts'].count).to eq 1
end
end
end
end
spec/spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'webmock/rspec'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
require 'spree/testing_support/factories'
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
config.include FactoryGirl::Syntax::Methods
config.include Devise::TestHelpers, :type => :controller
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
end
spec/support/vcr.rb:
require 'vcr'
VCR.configure do |config|
config.hook_into :webmock
config.ignore_localhost = true
config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
config.configure_rspec_metadata!
config.preserve_exact_body_bytes { true }
config.allow_http_connections_when_no_cassette = true
end
No Comments