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