From f74a0d4932290b6b696cadea5194892a22df3ab8 Mon Sep 17 00:00:00 2001 From: Ruben Ascencio Date: Tue, 22 Feb 2011 23:18:58 -0800 Subject: [PATCH] Initial commit --- .gitignore | 6 + Gemfile | 4 + LICENSE | 19 +++ README.markdown | 137 ++++++++++++++++++ Rakefile | 2 + acts_as_opengraph.gemspec | 23 +++ lib/acts_as_opengraph.rb | 10 ++ .../active_record/acts/opengraph.rb | 121 ++++++++++++++++ .../helper/acts_as_opengraph_helper.rb | 48 ++++++ lib/acts_as_opengraph/version.rb | 3 + test/opengraph_test.rb | 109 ++++++++++++++ test/test_helper.rb | 48 ++++++ 12 files changed, 530 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.markdown create mode 100644 Rakefile create mode 100644 acts_as_opengraph.gemspec create mode 100644 lib/acts_as_opengraph.rb create mode 100644 lib/acts_as_opengraph/active_record/acts/opengraph.rb create mode 100644 lib/acts_as_opengraph/helper/acts_as_opengraph_helper.rb create mode 100644 lib/acts_as_opengraph/version.rb create mode 100644 test/opengraph_test.rb create mode 100644 test/test_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb6a58a --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +pkg/* +*.gem +.bundle +doc/* +Gemfile.lock +.yardoc diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..17d0862 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in acts_as_opengraph.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..663f9f8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Ruben Ascencio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..85bcafd --- /dev/null +++ b/README.markdown @@ -0,0 +1,137 @@ +# acts\_as\_opengraph + +ActiveRecord extension that turns your models into [facebook opengraph](http://developers.facebook.com/docs/opengraph/) objects. + +## Installation + + gem install acts_as_opengraph + +Now just add the gem dependency in your projects configuration. + +## Usage + +### Adding acts\_as\_opengraph + + # app/models/movie.rb + class Movie < ActiveRecord::Base + acts_as_opengraph + end + +### Generating the opengraph meta tags + + # app/views/layouts/application.html.erb + + <%= yield :opengraph_meta_tags %> + + + # app/views/movies/show.html.erb + <% content_for :opengraph_meta_tags, opengraph_meta_tags_for(@movie) %> + +### Displaying the Like Button + # app/views/movies/show.html.erb + <%= like_button_for @movie %> + +\* Notice that the Like Button will retrieve the required `href` attribute by calling `@movie.opengraph_url`. Read below for more options. + + +## Options + +### Database columns + +Even when the names of these columns can be changed with configuration, `acts_as_opengraph` tries to guess these names by checking for the existence of common names. Chances are that your model already has some of the opengraph defined properties. + +This is the list of supported opengraph protocol properties and their possible column names (in precedence order): + +* __title__ - og\_title, title, name +* __type__ - og\_type, kind, category +* __image__ - og\_image, image, photo, picture, thumb, thumbnail +* __url__ - og\_url, url, uri, link +* __description__ - og\_description, description, summary +* __site\_name__ - og\_site, website, web +* __latitude__ - og\_latitude, latitude +* __longitude__ - og\_longitude, longitude +* __street\_address__ - og\_street\_address, street_address, address, street +* __locality__ - og\_locality, locality +* __region__ - og\_region, region +* __postal\_code__ - og\_postal\_code, postal\_code, zip\_code, zip +* __country\_name__ - og\_country_name, country\_name, country +* __email__ - og\_email, email, mail +* __phone\_number__ - og\_phone\_number, phone\_number, phone +* __fax\_number__ - og\_fax\_number, fax\_number, fax + +### Using a different column name + +If you need to use a different column then use the __columns__ option. For example, if you store the url of your movies using the `imdb_url` column in your movies table, then do this: + + # app/models/movie.rb + acts_as_opengraph :columns => { :url => :imdb_url } + +### What about using a custom method? + +If you wish to use a custom method for some opengraph field, then all you need to do is to define a method with the prefix `opengraph_`. +For example, if you are using [Paperclip](https://github.com/thoughtbot/paperclip) for your image attachments, you can do this: + + # app/models/movie.rb + class Movie < ActiveRecord::Base + + has_attached_file :picture, :styles => { :small => "160x130>"} + + acts_as_opengraph + + def opengraph_image + picture.url(:small) + end + + end + +### Default values + +Use the __values__ option for passing default opengraph values. For our Movie example we can specify that all of our records are movies by doing this: + + acts_as_opengraph :values => { :type => "movie" } + +\* Notice that `acts_as_opengraph` only accepts an options hash argument, so if you want to combine default values and column names you'd do this: + + acts_as_opengraph :columns => { :url => :imdb_url, :email => :contact }, + :values => { :type => "movie", :site_name => "http://example.com" } + +## Like Button options + +Along with the object for which you want to display the Like button, you can pass an options hash to configure its appearance: + + # app/views/layouts/application.html.erb + <%= like_button_for @movie, :layout => :box_count, :show_faces => true %> + +### Using url helpers + +By default, `acts_as_opengraph` will try to retrieve your object's url by calling `opengraph_url` on it. You could override it by defining a custom method, like this: + + # app/models/movie.rb + def opengraph_url + "http://example.com/movies/#{self.id}" + end + +But that's not the Rails way, so instead of doing that, you can pass an `href` option from your views, which means you can easily take advantage of the url helpers, like this: + + # app/views/movies/show.html.erb + <%= like_button_for @movie, :href => movie_path(@movie) %> + +See the complete list of allowed attributes and options [here](http://developers.facebook.com/docs/reference/plugins/like/). + +## Note on Patches/Pull Requests + +* Fork the project. +* Make your feature addition or bug fix. +* Add tests for it. This is important so I don’t break it in a future version unintentionally. +* Send me a pull request. Bonus points for topic branches. + + +## Copyright + +Copyright © 2011 Ruben Ascencio, released under the MIT license + + + + + + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..09b166a --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +require 'bundler' +Bundler::GemHelper.install_tasks \ No newline at end of file diff --git a/acts_as_opengraph.gemspec b/acts_as_opengraph.gemspec new file mode 100644 index 0000000..3e7b1f3 --- /dev/null +++ b/acts_as_opengraph.gemspec @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "acts_as_opengraph/version" + +Gem::Specification.new do |s| + s.name = "acts_as_opengraph" + s.version = ActsAsOpengraph::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Ruben Ascencio"] + s.email = ["galateaweb@gmail.com"] + s.homepage = "https://github.com/rubenrails/acts_as_opengraph" + s.summary = %q{ActiveRecord extension that turns your models into graph objects} + s.description = %q{ActiveRecord extension that turns your models into graph objects. Includes helper methods for adding tags and the Like Button to your views.} + + s.rubyforge_project = "acts_as_opengraph" + + s.add_development_dependency('sqlite3') + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] +end diff --git a/lib/acts_as_opengraph.rb b/lib/acts_as_opengraph.rb new file mode 100644 index 0000000..741bbaa --- /dev/null +++ b/lib/acts_as_opengraph.rb @@ -0,0 +1,10 @@ +if defined? ActiveRecord::Base + require File.join(File.dirname(__FILE__), 'acts_as_opengraph', 'active_record', 'acts', 'opengraph') + ActiveRecord::Base.send :include, ActiveRecord::Acts::Opengraph +end + +if defined? ActionView::Base + require File.join(File.dirname(__FILE__), 'acts_as_opengraph', 'helper', 'acts_as_opengraph_helper') + ActionView::Base.send :include, ActsAsOpengraphHelper +end + diff --git a/lib/acts_as_opengraph/active_record/acts/opengraph.rb b/lib/acts_as_opengraph/active_record/acts/opengraph.rb new file mode 100644 index 0000000..9eaf8a2 --- /dev/null +++ b/lib/acts_as_opengraph/active_record/acts/opengraph.rb @@ -0,0 +1,121 @@ +module ActiveRecord + module Acts + module Opengraph + + def self.included(base) + base.extend ActMethods + end + + module ActMethods + def acts_as_opengraph(options = {}) + # don't allow multiple calls + return if included_modules.include? InstanceMethods + + extend ClassMethods + + opengraph_atts = %w(title type image url description site_name latitude longitude street_address locality region postal_code country_name email phone_number fax_number) + + options[:columns] ||= {} + options[:values] ||= {} + + opengraph_atts.each do |att_name| + options[:columns]["#{att_name}".to_sym] ||= alternative_column_name_for("og_#{att_name}".to_sym) + end + + write_inheritable_attribute :opengraph_atts, opengraph_atts + class_inheritable_reader :opengraph_atts + + write_inheritable_attribute :options, options + class_inheritable_reader :options + + opengraph_atts.each do |att_name| + define_method "opengraph_#{att_name}" do + return_value_or_default att_name.to_sym + end + end + + include InstanceMethods + + end + + end + + module ClassMethods + + private + + # Returns a list of possible column names for a given attribute. + # + # @param [Symbol] att_name An opengraph attribute name prefixed with 'og_', i.e. :og_title, :og_type, etc + # @return [Array] A list of possible names for the given opengraph attribute + def alternative_names_for(att_name) + case att_name + when :og_title then [:title, :name] + when :og_type then [:kind, :category] + when :og_image then [:image, :photo, :picture, :thumb, :thumbnail] + when :og_url then [:url, :uri, :link] + when :og_description then [:description, :summary] + when :og_site_name then [:site, :website, :web] + when :og_latitude then [:latitude] + when :og_longitude then [:longitude] + when :og_street_address then [:street_address, :address, :street] + when :og_locality then [:locality] + when :og_region then [:region] + when :og_postal_code then [:postal_code, :zip_code, :zip] + when :og_country_name then [:country_name, :country] + when :og_email then [:email, :mail] + when :og_phone_number then [:phone_number, :phone] + when :og_fax_number then [:fax_number, :fax] + else [] + end + end + + # Tries to guess the column name for the given attribute. If it can't find any column (or similar) then it will create a virtual attribute + # for the object called: ATT_NAME_placeholder, so the object responds to that column. + # + # @param [Symbol] att_name An opengraph attribute name prefixed with 'og_', i.e. :og_title, :og_type, etc + # @return [String] The final name (found or created) for the opengraph attribute + def alternative_column_name_for(att_name) + alt_names = alternative_names_for(att_name) + columns_to_check = [att_name] + alt_names + columns_to_check.each do |column_name| + return column_name.to_sym if column_names.include?(column_name.to_s) + end + + # Define placeholder method + ph_method_name = "#{alt_names.first}_placeholder" + define_method(ph_method_name) { "" } + ph_method_name + end + + end + + module InstanceMethods + # Returns an array of hashes representing the opengraph attribute/values for the Object. + # + # @return [Array] List of hashes representing opengraph attribute/values + # @example + # @movie.opengraph_data #=> {name=> "og:title", :value => "The Rock"}, {:name => "og:type", :value=> "movie"} + def opengraph_data + data_list = opengraph_atts.map do |att_name| + {:name => "og:#{att_name}", :value => self.send("opengraph_#{att_name}")} + end + data_list.delete_if{ |el| el[:value].blank? } + end + + + private + + def return_value_or_default(att_name) + if options[:values].has_key?(att_name.to_sym) + options[:values][att_name] + else + self.send options[:columns]["#{att_name}".to_sym] + end + end + + end + + end + end +end diff --git a/lib/acts_as_opengraph/helper/acts_as_opengraph_helper.rb b/lib/acts_as_opengraph/helper/acts_as_opengraph_helper.rb new file mode 100644 index 0000000..e013dd2 --- /dev/null +++ b/lib/acts_as_opengraph/helper/acts_as_opengraph_helper.rb @@ -0,0 +1,48 @@ +module ActsAsOpengraphHelper + # Generates the opengraph meta tags for your views + # + # @param [Object, #opengraph_data] obj An instance of your ActiveRecord model that responds to opengraph_data + # @return [String] A set of meta tags describing your graph object based on the {http://ogp.me/ opengraph protocol} + # @raise [ArgumentError] When you pass an instance of an object that doesn't responds to opengraph_data (maybe you forgot to add acts_as_opengraph in your model) + # @example + # opengraph_meta_tags_for(@movie) + def opengraph_meta_tags_for(obj) + raise(ArgumentError.new, "You need to call acts_as_opengraph on your #{obj.class} model") unless obj.respond_to?(:opengraph_data) + tags = obj.opengraph_data.map do |att| + %() + end + tags = tags.join("\n") + tags.respond_to?(:html_safe) ? tags.html_safe : tags + end + + # Displays the Facebook Like Button in your views. + # + # @param [Object, #opengraph_data] obj An instance of your ActiveRecord model that responds to opengraph_data + # @param [Hash] options A Hash of {http://developers.facebook.com/docs/reference/plugins/like/ supported attributes}. Defaults to { :layout => :standard, :show_faces => false, :width => 450, :action => :like, :colorscheme => :light } + # @return [String] An iFrame version of the Facebook Like Button + # @raise [ArgumentError] When you pass an instance of an object that doesn't responds to opengraph_data (maybe you forgot to add acts_as_opengraph in your model) + # @example + # like_button_for(@movie) + # like_button_for(@movie, :layout => :button_count, :display_faces => true) + # @example Specifying href using rails helpers + # like_button_for(@movie, :href => movie_url(@movie)) + def like_button_for(obj, options = {}) + raise(ArgumentError.new, "You need to call acts_as_opengraph on your #{obj.class} model") unless obj.respond_to?(:opengraph_data) + href = options[:href] ? options[:href] : obj.opengraph_url + return unless href.present? + + config = { :layout => :standard, :show_faces => false, :width => 450, :action => :like, :colorscheme => :light } + config.update(options) if options.is_a?(Hash) + + o_layout = config[:layout].to_sym + if o_layout == :standard + config[:height] = config[:show_faces].to_s.to_sym == :true ? 80 : 35 + elsif o_layout == :button_count + config[:height] = 21 + elsif o_layout == :box_count + config[:height] = 65 + end + + %() + end +end \ No newline at end of file diff --git a/lib/acts_as_opengraph/version.rb b/lib/acts_as_opengraph/version.rb new file mode 100644 index 0000000..6d226b3 --- /dev/null +++ b/lib/acts_as_opengraph/version.rb @@ -0,0 +1,3 @@ +module ActsAsOpengraph + VERSION = "0.0.1" +end diff --git a/test/opengraph_test.rb b/test/opengraph_test.rb new file mode 100644 index 0000000..0b45f6f --- /dev/null +++ b/test/opengraph_test.rb @@ -0,0 +1,109 @@ +require File.join File.dirname(__FILE__), 'test_helper' + +class Book < ActiveRecord::Base + acts_as_opengraph +end + +class Movie < ActiveRecord::Base + acts_as_opengraph :values => {:type => "movie", :site_name => "IMDb"}, :columns => {:url => :imdb} + + def opengraph_image + "http://ia.media-imdb.com/rock.jpg" + end + +end + +class Song < ActiveRecord::Base + # This model doesn't uses acts_as_opengraph +end + +class MovieTest < Test::Unit::TestCase + + include ActsAsOpengraphHelper + + MOVIE_NAME = "The Rock" + MOVIE_DESCRIPTION = "A renegade general and his group of U.S. Marines take over Alcatraz and threaten San Francisco Bay with biological weapons." + MOVIE_URL = "http://www.imdb.com/title/tt0117500/" + + GENERATED_OPENGRAPH_DATA = [ + {:value=> MOVIE_NAME, :name=> "og:title"}, + {:value=> "movie", :name=> "og:type"}, + {:value=> "http://ia.media-imdb.com/rock.jpg", :name=> "og:image"}, + {:value=> MOVIE_URL, :name=> "og:url"}, + {:value=> MOVIE_DESCRIPTION, :name=> "og:description"}, + {:value=> "IMDb", :name=> "og:site_name"} + ] + + GENERATED_META_TAGS = %( + + + + +) + + GENERATED_LIKE_BUTTON = %() + GENERATED_LIKE_BUTTON_CUSTOM_URL = %() + def setup + setup_db + assert @movie = Movie.create!(:title => MOVIE_NAME, :description => MOVIE_DESCRIPTION, :imdb => MOVIE_URL) + assert @song = Song.create!(:title => "Yellow Submarine") + assert @book = Book.create!(:title => "Getting real") + end + + def teardown + teardown_db + end + + def test_respond_to_opengraph_methods + assert_respond_to @movie, :opengraph_title + assert_respond_to @movie, :opengraph_type + assert_respond_to @movie, :opengraph_image + assert_respond_to @movie, :opengraph_url + assert_respond_to @movie, :opengraph_description + assert_respond_to @movie, :opengraph_site_name + assert_respond_to @movie, :opengraph_latitude + assert_respond_to @movie, :opengraph_longitude + assert_respond_to @movie, :opengraph_street_address + assert_respond_to @movie, :opengraph_locality + assert_respond_to @movie, :opengraph_region + assert_respond_to @movie, :opengraph_postal_code + assert_respond_to @movie, :opengraph_country_name + assert_respond_to @movie, :opengraph_email + assert_respond_to @movie, :opengraph_phone_number + assert_respond_to @movie, :opengraph_fax_number + assert_respond_to @movie, :opengraph_data + end + + def test_opengraph_data + assert_equal GENERATED_OPENGRAPH_DATA, @movie.opengraph_data + end + + def test_default_values + assert_equal "IMDb", @movie.opengraph_site_name + end + + def test_method_overriding + assert_equal "http://ia.media-imdb.com/rock.jpg", @movie.opengraph_image + end + + def test_different_column_name + assert_equal MOVIE_URL, @movie.opengraph_url + end + + def test_meta_tags_helper + assert_equal GENERATED_META_TAGS, opengraph_meta_tags_for(@movie) + assert_raise(ArgumentError) { opengraph_meta_tags_for(@song) } + end + + def test_like_button_helper + assert_equal GENERATED_LIKE_BUTTON, like_button_for(@movie) + assert_equal GENERATED_LIKE_BUTTON_CUSTOM_URL, like_button_for(@movie, :href => "http://example.com/movies/6") + + # There's no way of getting the href attribute for this Book, so it returns nil + assert_nil like_button_for(@book) + + # We aren't using acts_as_opengraph for this model, so it should let us know + assert_raise(ArgumentError) { like_button_for(@song) } + end + +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..9b61100 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,48 @@ +plugin_path = File.join File.dirname(__FILE__), '..' + +require 'test/unit' + +require 'rubygems' +require 'active_record' +require 'action_view' + + + +ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:") +$stdout = StringIO.new + +def setup_db + ActiveRecord::Base.logger + ActiveRecord::Schema.define(:version => 1) do + + create_table :books, :force => true do |t| + t.string :title + end + + create_table :movies, :force => true do |t| + t.string :title + t.string :description + t.string :imdb + end + + create_table :songs, :force => true do |t| + t.string :title + end + + end +end + +def teardown_db + ActiveRecord::Base.connection.tables.each do |table| + ActiveRecord::Base.connection.drop_table(table) + end +end + +setup_db + + +$:.unshift File.join plugin_path, 'lib' + +require 'acts_as_opengraph/active_record/acts/opengraph' +require 'acts_as_opengraph/helper/acts_as_opengraph_helper' +require 'acts_as_opengraph' \ No newline at end of file