scrapbooker 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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