scrapbooker 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ *.log
3
+ *.sqlite3
4
+ *.gem
5
+ .rvmrc
6
+ .bundle
7
+ Gemfile.lock
8
+ pkg/*
9
+ spec/support/tmp/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,21 @@
1
+ # Scrapbooker Changelog
2
+
3
+ ## 1.0 (April 6th, 2012)
4
+
5
+ Changed gem name and releasing version 1.0
6
+
7
+ ## 0.1.2.1 (March 6th, 2012)
8
+
9
+ Fix for live gem.
10
+
11
+ ## 0.1.2 (March 6th, 2012)
12
+
13
+ Basic generator functionality added.
14
+
15
+ ## 0.1.1 (March 5th, 2012)
16
+
17
+ ContrarySheep added to authors.
18
+
19
+ ## 0.1 (March 3rd, 2012)
20
+
21
+ Initial project creation.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Christopher Griffith Studio
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,79 @@
1
+ # Scrapbooker
2
+
3
+ Scrapbooker is a gem for rails that allows you to pull information from social media feeds then store the information locally. This is handy for keeping an archive of activity, but also a good way to make that content available to your Rails app.
4
+
5
+ ## Installation
6
+
7
+ The scrapbooker gem makes use of railties, and as such requires Rails version >= 3.0.
8
+
9
+ Installing the gem is as simple as adding it your gemfile.
10
+
11
+ gem "scrapbooker"
12
+
13
+ This will also install the latest version of the gem, plus any dependencies that you may need to connect to the various services including:
14
+
15
+ + twitter
16
+ + simple_oauth
17
+ + addressable
18
+ + multipart-post
19
+ + faraday
20
+
21
+ ## Usage
22
+
23
+ The gem provides a generator for getting started quickly. Running the generator will create a model and migration in you app. As an example, I can to create a new class called `Scrapbook` by running the following command.
24
+
25
+ rails g scrapbooker Scrapbook
26
+
27
+ Once the new scrapbook migration has been created, run the migration.
28
+
29
+ rake db:migrate
30
+
31
+ The new `scrapbook.rb` model contains `extend_with_scrapbooker`. This extends the class with the scrapbooker functionality. The scrapbooker configuration can be set by passing the settings in a `configure` block.
32
+
33
+ In the example, the Scrapbook class is going to store tweets from [@GriffithStudio](http://twitter.com/GriffithStudio) and check for new tweets every hour. In the `scrapbook.rb` model, the following should be added.
34
+
35
+ self.configure do |c|
36
+ c.update_interval = 60
37
+ c.twitter_feed = { :username => 'GriffithStudio' }
38
+ end
39
+
40
+ Now, the method `update_scrapbook` can be called on the Scrapbook class to pull any new tweets into the application. A before_filter can also be setup in the controller to perform the update at the specified interval. For this, the following can be added to the application controller.
41
+
42
+ before_filter :update_scrapbook
43
+
44
+ private
45
+
46
+ def update_scrapbook
47
+ Scrapbook.update_scrapbook?
48
+ end
49
+
50
+ The Scrapbook class can be used anywhere in the application, just as any other class. for instance the following calls the first Scrapbook entry.
51
+
52
+ Scrapbook.first
53
+
54
+ Scrapbooker also extends the Scrapbook class with the a few unique attributes.
55
+
56
+ Scrapbook.first.original_tweet
57
+ Scrapbook.first.is_retweet
58
+ Scrapbook.first.links
59
+ Scrapbook.first.media
60
+ Scrapbook.first.mentions
61
+ Scrapbook.first.hashtags
62
+
63
+ In the application views, scrapbooker adds a few helper methods.
64
+
65
+ render_tweet Scrapbook.first
66
+
67
+ The helper methods can all be overridden in the application helpers.
68
+
69
+ ## Testing
70
+
71
+ Testing the gem makes use of the [`vcr`](https://github.com/myronmarston/vcr) gem from Myron Marston in order to playback specific HTTP responses. This is fantastic for testing, but be warned that the tests are very brittle.
72
+
73
+ ## Credits
74
+
75
+ The gem is being developed as a collaboration between [@GriffithStudio](http://twitter.com/GriffithStudio) and [@ContrarySheep](http://twitter.com/ContrarySheep).
76
+
77
+ ##License
78
+
79
+ Scrapbooker is (c) 2012 Christopher Griffith Studio. It is free software, and may be redistributed under the terms specified in the LICENSE file.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "scrapbooker"
@@ -0,0 +1,10 @@
1
+ Description:
2
+ The scrapbooker generator creates a model and migration in your Rails app. Where the NAME argument is the name of your model.
3
+
4
+ Example:
5
+
6
+ >> rails generate scrapbooker Scrapbook
7
+
8
+ Creates a new model with the class "Scrapbook" and a migration for a table called "scrapbooks".
9
+
10
+
@@ -0,0 +1,55 @@
1
+ require 'rails/generators'
2
+
3
+ class ScrapbookerGenerator < Rails::Generators::NamedBase
4
+ include Rails::Generators::Migration
5
+
6
+ source_root File.expand_path("../templates",__FILE__)
7
+
8
+ attr_accessor :class_name
9
+
10
+ def initialize(args,*)
11
+ super
12
+ given_name = args.first
13
+ @class_name = test_singularity(given_name) ? given_name.underscore : given_name.singularize.underscore
14
+ end
15
+
16
+ def create_model
17
+ template "model.rb", "app/models/#{@class_name}.rb"
18
+ end
19
+
20
+ def create_migration
21
+ migration_template "migration.rb", "db/migrate/#{migration_file_name}"
22
+ end
23
+
24
+ @@migrations = false
25
+
26
+ def self.next_migration_number(dirname)
27
+ if ActiveRecord::Base.timestamped_migrations
28
+ if @@migrations
29
+ (current_migration_number(dirname) + 1)
30
+ else
31
+ @@migrations = true
32
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
33
+ end
34
+ else
35
+ "%.3d" % (current_migration_number(dirname) + 1)
36
+ end
37
+ end
38
+
39
+
40
+ protected
41
+
42
+ def migration_name
43
+ "create_#{@class_name.pluralize}"
44
+ end
45
+
46
+ def migration_file_name
47
+ "#{migration_name}.rb"
48
+ end
49
+
50
+ def test_singularity(str)
51
+ str.pluralize != str and str.singularize == str
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,17 @@
1
+ class Create<%= @class_name.pluralize.camelize -%> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= @class_name.pluralize -%> do |t|
4
+ t.boolean :display, :default => true
5
+ t.text :entry
6
+ t.datetime :posted_at
7
+ t.text :raw_feed
8
+ t.string :source
9
+ t.integer :source_id, :limit => 8
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table :<%= @class_name.pluralize -%>
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ class <%= @class_name.camelize -%> < ActiveRecord::Base
2
+
3
+ extend_with_scrapbooker
4
+
5
+ #
6
+ # Example Scrapbooker configuration settings. Refer to documentation for other configuration options.
7
+ #
8
+ # self.configure do |c|
9
+ # c.update_interval = 60 # In minutes
10
+ # c.twitter_feed = { :username => 'username', :include_retweets => true }
11
+ # end
12
+ #
13
+
14
+ end
@@ -0,0 +1,11 @@
1
+ require "generators/scrapbooker"
2
+
3
+ require "scrapbooker/railtie" if defined? Rails
4
+ require "scrapbooker/base"
5
+ require "scrapbooker/configuration"
6
+ require "scrapbooker/utilities"
7
+ require "scrapbooker/helpers/helpers"
8
+ require "scrapbooker/services/services"
9
+
10
+ module Scrapbooker
11
+ end
@@ -0,0 +1,20 @@
1
+ module Scrapbooker
2
+ module Base
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def extend_with_scrapbooker
9
+
10
+ include Scrapbooker::Services
11
+
12
+ extend Scrapbooker::Utilities
13
+ extend Scrapbooker::Services
14
+
15
+ validates_uniqueness_of :source_id, :scope => :source
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ module Scrapbooker
2
+ class Configuration
3
+
4
+ def update_interval=(minutes)
5
+ @interval = minutes
6
+ end
7
+
8
+ def update_interval
9
+ @interval = @interval ? @interval : 1440 #The default interval in minutes (24 hours)
10
+ end
11
+
12
+ def timestamp_storage=(path)
13
+ @timestamp_storage = path
14
+ end
15
+
16
+ def timestamp_storage
17
+ @timestamp_storage = @timestamp_storage ? @timestamp_storage : "#{Rails.root}/tmp"
18
+ end
19
+
20
+ def twitter_feed=(options)
21
+ @twitter_feed = options
22
+ end
23
+
24
+ def twitter_feed
25
+ @twitter_feed
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ require "scrapbooker/helpers/tweet_helpers"
2
+
3
+ module Scrapbooker
4
+ module Helpers
5
+ include TweetHelpers
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ module Scrapbooker
2
+ module Helpers
3
+ module TweetHelpers
4
+
5
+ def render_tweet(tweet)
6
+ rendered_tweet = tweet.entry
7
+ tweet.hashtags.each { |hashtag| expand_hashtag(hashtag,rendered_tweet) }
8
+ tweet.mentions.each { |mention| expand_mention(mention,rendered_tweet) }
9
+ tweet.links.each { |link| expand_link(link,rendered_tweet) }
10
+ tweet.media.each { |media| expand_media(media,rendered_tweet) }
11
+ return raw rendered_tweet
12
+ end
13
+
14
+ def expand_hashtag(hashtag,tweet)
15
+ tweet.sub!("#{hashtag}", link_to(hashtag,"http://twitter.com/search/#{URI.escape(hashtag)}",:target => :blank))
16
+ end
17
+
18
+ def expand_link(link,tweet)
19
+ tweet.sub!(link['display'], link_to(link['source'],link['source'],:target => :blank))
20
+ end
21
+
22
+ def expand_media(media,tweet)
23
+ tweet.sub!(media['display'], link_to(media['source'],media['source'],:target => :blank))
24
+ end
25
+
26
+ def expand_mention(mention,tweet)
27
+ tweet.sub!(/@#{mention}/i, link_to("@#{mention}","http://twitter.com/#{mention}",:target => :blank))
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module Scrapbooker
2
+ class Railtie < Rails::Railtie
3
+ initializer 'scrapbooker.initialize' do
4
+ ActiveSupport.on_load(:active_record) do
5
+ ActiveRecord::Base.send :include, Scrapbooker::Base
6
+ ActionView::Base.send :include, Scrapbooker::Helpers
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require "scrapbooker/services/twitter_feed"
2
+
3
+ module Scrapbooker
4
+ module Services
5
+
6
+ def twitter_feed
7
+ @twitter_feed ||= TwitterFeed.new(self.configuration.twitter_feed)
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,117 @@
1
+ require 'twitter'
2
+
3
+ module Scrapbooker
4
+ module Services
5
+
6
+ class TwitterFeed
7
+ def self.default_options
8
+ @default_options ||= {
9
+ :username => nil,
10
+ :include_retweets => true,
11
+ :exclude_replies => true,
12
+ :include_entities => true,
13
+ :count => 20,
14
+ :trim_user => true
15
+ }
16
+ end
17
+
18
+ attr_reader :username, :include_retweets, :exclude_replies, :include_entities, :count, :trim_user, :active
19
+
20
+ def initialize(options)
21
+ @active = options ? true : false
22
+ options ||= {}
23
+ options = self.class.default_options.merge(options)
24
+ @username = options[:username]
25
+ @include_retweets = options[:include_retweets]
26
+ @exclude_replies = options[:exclude_replies]
27
+ @include_entities = options[:include_entities]
28
+ @count = options[:count]
29
+ @trim_user = options[:trim_user]
30
+ end
31
+ end
32
+
33
+ def poll_twitter?
34
+ poll_twitter if self.twitter_feed.active
35
+ end
36
+
37
+ def poll_twitter
38
+ results = Twitter.user_timeline(self.twitter_feed.username, {
39
+ :include_rts => self.twitter_feed.include_retweets,
40
+ :include_replies => self.twitter_feed.exclude_replies,
41
+ :include_entities => self.twitter_feed.include_entities,
42
+ :count => self.twitter_feed.count,
43
+ :trim_user => self.twitter_feed.trim_user
44
+ })
45
+ for tweet in results do
46
+ store_tweet(tweet)
47
+ end
48
+ end
49
+
50
+ def store_tweet(tweet)
51
+ self.create(
52
+ :source => 'twitter',
53
+ :source_id => tweet.id,
54
+ :raw_feed => tweet.instance_variable_get('@attrs').to_yaml,
55
+ :posted_at => tweet.created_at,
56
+ :entry => tweet.text
57
+ )
58
+ end
59
+
60
+ module TweetMethods
61
+
62
+ def original_tweet
63
+ YAML::load(self.raw_feed)
64
+ end
65
+
66
+ def is_retweet
67
+ self.original_tweet['retweeted_status'] ? true : false
68
+ end
69
+
70
+ def links
71
+ links = Array.new
72
+ if self.original_tweet['entities'] && self.original_tweet['entities']['urls']
73
+ for link in self.original_tweet['entities']['urls'] do
74
+ link_hash = Hash['source', link['expanded_url'], 'display', link['url']]
75
+ links << link_hash
76
+ end
77
+ end
78
+ return links
79
+ end
80
+
81
+ def media
82
+ media = Array.new
83
+ if self.original_tweet['entities'] && self.original_tweet['entities']['media']
84
+ for entity in self.original_tweet['entities']['media'] do
85
+ media_hash = Hash['source', entity['media_url'], 'display', entity['url'], 'type', entity['type']]
86
+ media << media_hash
87
+ end
88
+ end
89
+ return media
90
+ end
91
+
92
+ def mentions
93
+ mentions = Array.new
94
+ if self.original_tweet['entities'] && self.original_tweet['entities']['user_mentions']
95
+ for mention in self.original_tweet['entities']['user_mentions'] do
96
+ mentions << "#{mention['screen_name']}"
97
+ end
98
+ end
99
+ return mentions
100
+ end
101
+
102
+ def hashtags
103
+ hashtags = Array.new
104
+ if self.original_tweet['entities'] && self.original_tweet['entities']['hashtags']
105
+ for hashtag in self.original_tweet['entities']['hashtags'] do
106
+ hashtags << "##{hashtag['text']}"
107
+ end
108
+ end
109
+ return hashtags
110
+ end
111
+
112
+ end
113
+
114
+ include TweetMethods
115
+
116
+ end
117
+ end
@@ -0,0 +1,44 @@
1
+ module Scrapbooker
2
+ module Utilities
3
+
4
+ def configure
5
+ yield configuration
6
+ end
7
+
8
+ def configuration
9
+ @configuration ||= Configuration.new
10
+ end
11
+
12
+ def update_scrapbook?
13
+ update_scrapbook if !timestamp_exists?
14
+ update_scrapbook if !timestamp_is_current?
15
+ end
16
+
17
+ def update_scrapbook
18
+ #Get results from active services
19
+ poll_twitter?
20
+ update_timestamp
21
+ end
22
+
23
+ def timestamp_is_current?
24
+ last_update > self.configuration.update_interval.minutes.ago
25
+ end
26
+
27
+ def timestamp_exists?
28
+ File.exists?(timestamp_file) ? true : false
29
+ end
30
+
31
+ def update_timestamp
32
+ File.open(timestamp_file, "w+") {|f| f.write(Time.now)}
33
+ end
34
+
35
+ def timestamp_file
36
+ "#{self.configuration.timestamp_storage}/#{self.name.underscore}_last_update"
37
+ end
38
+
39
+ def last_update
40
+ Time.parse(File.read(timestamp_file))
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Scrapbooker
2
+ VERSION = "1.0"
3
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "scrapbooker/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "scrapbooker"
7
+ s.version = Scrapbooker::VERSION
8
+ s.authors = ["Christopher Griffith Studio","ContrarySheep"]
9
+ s.email = ["studio@christophergriffith.com","contrarysheep@gmail.com"]
10
+ s.homepage = "http://griffithstudio.github.com/scrapbooker"
11
+ s.summary = %q{Scrapbooker saves social media feeds locally.}
12
+ s.description = %q{Scrapbooker is a gem for rails that allows you to pull information from social media feeds then store the information locally.}
13
+
14
+ s.rubyforge_project = "scrapbooker"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here;
22
+ s.add_dependency 'rails', '>= 3.0'
23
+ s.add_dependency 'json'
24
+ s.add_dependency 'twitter'
25
+
26
+ # specify development depencies here;
27
+ s.add_development_dependency 'rspec'
28
+ s.add_development_dependency 'vcr', '~> 2.0.0'
29
+ s.add_development_dependency 'fakeweb'
30
+ s.add_development_dependency 'sqlite3'
31
+
32
+ # specify any runtime dependencies here;
33
+
34
+ end
@@ -0,0 +1,4 @@
1
+ class Scrapbook < ActiveRecord::Base
2
+ include Scrapbooker::Base
3
+ extend_with_scrapbooker
4
+ end
@@ -0,0 +1,11 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table "scrapbooks", :force => true do |t|
3
+ t.boolean :display, :default => true
4
+ t.text :entry
5
+ t.datetime :posted_at
6
+ t.text :raw_feed
7
+ t.string :source
8
+ t.integer :source_id, :limit => 8
9
+ t.timestamps
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Scrapbooker::Helpers::TweetHelpers do
4
+
5
+ before(:all) do
6
+ @helper = class Helper
7
+ include ActionView::Helpers
8
+ include Scrapbooker::Helpers
9
+ end.new
10
+ end
11
+
12
+ it "should return the tweet with hashtags linked" do
13
+ test = @helper.expand_hashtag('#testing','This is a test #testing')
14
+ result = "This is a test <a href=\"http://twitter.com/search/%23testing\" target=\"blank\">#testing</a>"
15
+ test.should eq result
16
+ end
17
+
18
+ it "should return the tweet with mentions linked" do
19
+ test = @helper.expand_mention('test','This is a test for @test')
20
+ result = "This is a test for <a href=\"http://twitter.com/test\" target=\"blank\">@test</a>"
21
+ test.should eq result
22
+ end
23
+
24
+ it "should return the tweet with links expanded and linked" do
25
+ link = { 'source' => 'http://test.com', 'display' => 'http://test.com'}
26
+ test = @helper.expand_link(link,'This is a test for http://test.com')
27
+ result = "This is a test for <a href=\"http://test.com\" target=\"blank\">http://test.com</a>"
28
+ test.should eq result
29
+ end
30
+
31
+ it "should return the tweet with media expanded and linked" do
32
+ media = { 'source' => 'http://test.com/test.jpg', 'display' => 'http://test.com/test.jpg'}
33
+ test = @helper.expand_media(media,'This is a test for http://test.com/test.jpg')
34
+ result = "This is a test for <a href=\"http://test.com/test.jpg\" target=\"blank\">http://test.com/test.jpg</a>"
35
+ test.should eq result
36
+ end
37
+
38
+ end
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+
3
+ describe Scrapbooker do
4
+
5
+ before(:each) do
6
+ clean_database!
7
+ reset_scrapbook
8
+ end
9
+
10
+ it "tests the timestamp file" do
11
+ Scrapbook.update_timestamp
12
+ Scrapbook.last_update.strftime("%H:%M:%S").should eq Time.now.strftime("%H:%M:%S")
13
+ end
14
+
15
+ it "tests the ability to add a database entry" do
16
+ scrapbook = Scrapbook.create(:source => "test")
17
+ scrapbook.source.should eq "test"
18
+ end
19
+
20
+ end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Scrapbooker::Services::TwitterFeed do
4
+
5
+ use_vcr_cassette "twitter_feed"
6
+
7
+ before(:all) do
8
+ clean_database!
9
+ reset_scrapbook
10
+ Scrapbook.configuration.twitter_feed = { :username => 'GriffithStudio',
11
+ :include_entities => true,
12
+ :exclude_replies => false,
13
+ :count => 50,
14
+ :trim_user => false }
15
+
16
+ end
17
+
18
+ it "polls twitter and stores tweets in the database" do
19
+ Scrapbook.poll_twitter?
20
+ Scrapbook.count.should > 0
21
+ end
22
+
23
+ it "checks for the retweet status" do
24
+ tweet = Scrapbook.find_by_source_id(169118526404165632)
25
+ tweet.is_retweet.should eq true
26
+ end
27
+
28
+ it "returns the links from a tweet" do
29
+ tweet = Scrapbook.find_by_source_id(187542378033332224)
30
+ tweet.links.first['display'].should eq "http://t.co/UAWQ5u27"
31
+ end
32
+
33
+ it "returns the media from a tweet" do
34
+ tweet = Scrapbook.find_by_source_id(185131786605563904)
35
+ tweet.media.first['display'].should eq "http://t.co/XiIWPrJn"
36
+ end
37
+
38
+ it "returns the mentions from a tweet" do
39
+ tweet = Scrapbook.find_by_source_id(181838740447182848)
40
+ tweet.mentions.first.should eq "mrwhite_"
41
+ end
42
+
43
+ it "returns the hashtags from a tweet" do
44
+ tweet = Scrapbook.find_by_source_id(172399167614881792)
45
+ tweet.hashtags.last.should eq "#desert"
46
+ end
47
+
48
+ end
@@ -0,0 +1,40 @@
1
+ $LOAD_PATH << "." unless $LOAD_PATH.include?(".")
2
+
3
+ require "rubygems"
4
+ require "rails/all"
5
+ require "scrapbooker"
6
+ require "twitter"
7
+ require "vcr"
8
+
9
+ VCR.configure do |config|
10
+ config.cassette_library_dir = 'spec/support/cassettes'
11
+ config.hook_into :fakeweb
12
+ config.default_cassette_options = { :record => :new_episodes }
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.mock_with :rspec
17
+ config.extend VCR::RSpec::Macros
18
+ config.treat_symbols_as_metadata_keys_with_true_values = true
19
+ end
20
+
21
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
22
+
23
+ ActiveRecord::Base.silence do
24
+ ActiveRecord::Migration.verbose = false
25
+ load(File.dirname(__FILE__) + '/schema.rb')
26
+ load(File.dirname(__FILE__) + '/models.rb')
27
+ end
28
+
29
+ def clean_database!
30
+ ActiveRecord::Base.connection.execute "DELETE FROM #{Scrapbook.table_name}"
31
+ end
32
+
33
+ clean_database!
34
+
35
+ def reset_scrapbook
36
+ Scrapbook.configuration.update_interval = nil
37
+ Scrapbook.configuration.twitter_feed = nil
38
+ # Set the timestamp_storage to the tmp directory in support.
39
+ Scrapbook.configuration.timestamp_storage = File.dirname(__FILE__) + '/support/tmp'
40
+ end
@@ -0,0 +1,183 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.twitter.com/1/statuses/user_timeline.json?include_rts=true&include_replies=false&include_entities=true&count=50&trim_user=false&screen_name=GriffithStudio
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ accept:
11
+ - application/json
12
+ user-agent:
13
+ - Twitter Ruby Gem 2.2.0
14
+ accept-encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ connection:
17
+ - close
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ date:
24
+ - Thu, 05 Apr 2012 21:53:30 GMT
25
+ status:
26
+ - 200 OK
27
+ x-ratelimit-class:
28
+ - api
29
+ etag:
30
+ - ! '"6f115a89394add2aedb4bb17c85c6d13"'
31
+ x-ratelimit-reset:
32
+ - '1333665736'
33
+ x-transaction-mask:
34
+ - a6183ffa5f8ca943ff1b53b5644ef1140428d3cb
35
+ pragma:
36
+ - no-cache
37
+ cache-control:
38
+ - no-cache, no-store, must-revalidate, pre-check=0, post-check=0
39
+ x-mid:
40
+ - 02e9a379d6b522f6a97a71043493c8deb57784d0
41
+ x-ratelimit-limit:
42
+ - '150'
43
+ expires:
44
+ - Tue, 31 Mar 1981 05:00:00 GMT
45
+ last-modified:
46
+ - Thu, 05 Apr 2012 21:53:30 GMT
47
+ content-type:
48
+ - application/json; charset=utf-8
49
+ x-ratelimit-remaining:
50
+ - '142'
51
+ x-runtime:
52
+ - '0.14167'
53
+ x-frame-options:
54
+ - SAMEORIGIN
55
+ x-transaction:
56
+ - 6b11aaef91832139
57
+ set-cookie:
58
+ - k=10.35.55.138.1333662810682999; path=/; expires=Thu, 12-Apr-12 21:53:30 GMT;
59
+ domain=.twitter.com
60
+ - guest_id=v1%3A133366281068694086; domain=.twitter.com; path=/; expires=Sun,
61
+ 06-Apr-2014 09:53:30 GMT
62
+ - _twitter_sess=BAh7CCIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%250ASGFzaHsABjoKQHVzZWR7ADoPY3JlYXRlZF9hdGwrCD%252BCf4Q2AToHaWQiJTc3%250AMTY3NGI5YjlhZTEwYTZlMTUwZTQ1OWI1NTk0Yjdi--a1704c6d223e9bfd66a3ae316c2fc45712beb2bd;
63
+ domain=.twitter.com; path=/; HttpOnly
64
+ vary:
65
+ - Accept-Encoding
66
+ content-encoding:
67
+ - gzip
68
+ content-length:
69
+ - '4743'
70
+ server:
71
+ - tfe
72
+ body:
73
+ encoding: ASCII-8BIT
74
+ string: !binary |-
75
+ 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
180
+ J2gY05B3/8f/ARsDraD46AAA
181
+ http_version: '1.1'
182
+ recorded_at: Thu, 05 Apr 2012 21:53:29 GMT
183
+ recorded_with: VCR 2.0.1
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scrapbooker
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christopher Griffith Studio
9
+ - ContrarySheep
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-04-06 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: &70168508446000 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70168508446000
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: &70168508445580 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70168508445580
37
+ - !ruby/object:Gem::Dependency
38
+ name: twitter
39
+ requirement: &70168508445120 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70168508445120
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ requirement: &70168508444700 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70168508444700
59
+ - !ruby/object:Gem::Dependency
60
+ name: vcr
61
+ requirement: &70168500181280 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 2.0.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70168500181280
70
+ - !ruby/object:Gem::Dependency
71
+ name: fakeweb
72
+ requirement: &70168500180860 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *70168500180860
81
+ - !ruby/object:Gem::Dependency
82
+ name: sqlite3
83
+ requirement: &70168500180400 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: *70168500180400
92
+ description: Scrapbooker is a gem for rails that allows you to pull information from
93
+ social media feeds then store the information locally.
94
+ email:
95
+ - studio@christophergriffith.com
96
+ - contrarysheep@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - .rspec
103
+ - CHANGELOG.md
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - init.rb
109
+ - lib/generators/USAGE
110
+ - lib/generators/scrapbooker.rb
111
+ - lib/generators/templates/migration.rb
112
+ - lib/generators/templates/model.rb
113
+ - lib/scrapbooker.rb
114
+ - lib/scrapbooker/base.rb
115
+ - lib/scrapbooker/configuration.rb
116
+ - lib/scrapbooker/helpers/helpers.rb
117
+ - lib/scrapbooker/helpers/tweet_helpers.rb
118
+ - lib/scrapbooker/railtie.rb
119
+ - lib/scrapbooker/services/services.rb
120
+ - lib/scrapbooker/services/twitter_feed.rb
121
+ - lib/scrapbooker/utilities.rb
122
+ - lib/scrapbooker/version.rb
123
+ - scrapbooker.gemspec
124
+ - spec/models.rb
125
+ - spec/schema.rb
126
+ - spec/scrapbooker/helpers/tweet_helpers_spec.rb
127
+ - spec/scrapbooker/scrapbooker_spec.rb
128
+ - spec/scrapbooker/services/twitter_feed_spec.rb
129
+ - spec/spec_helper.rb
130
+ - spec/support/cassettes/twitter_feed.yml
131
+ homepage: http://griffithstudio.github.com/scrapbooker
132
+ licenses: []
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project: scrapbooker
151
+ rubygems_version: 1.8.15
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: Scrapbooker saves social media feeds locally.
155
+ test_files:
156
+ - spec/models.rb
157
+ - spec/schema.rb
158
+ - spec/scrapbooker/helpers/tweet_helpers_spec.rb
159
+ - spec/scrapbooker/scrapbooker_spec.rb
160
+ - spec/scrapbooker/services/twitter_feed_spec.rb
161
+ - spec/spec_helper.rb
162
+ - spec/support/cassettes/twitter_feed.yml