stratify-base 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ .bundle
3
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.2-p180@stratify
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in stratify-base.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,52 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ stratify-base (0.1.0)
5
+ bson_ext (~> 1.3.1)
6
+ mongoid (~> 2.0.2)
7
+ tilt (~> 1.3.2)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ activemodel (3.1.0.rc1)
13
+ activesupport (= 3.1.0.rc1)
14
+ bcrypt-ruby (~> 2.1.4)
15
+ builder (~> 3.0.0)
16
+ i18n (~> 0.6.0beta1)
17
+ activesupport (3.1.0.rc1)
18
+ multi_json (~> 1.0)
19
+ bcrypt-ruby (2.1.4)
20
+ bson (1.3.1)
21
+ bson_ext (1.3.1)
22
+ builder (3.0.0)
23
+ database_cleaner (0.6.7)
24
+ diff-lcs (1.1.2)
25
+ i18n (0.6.0)
26
+ mocha (0.9.12)
27
+ mongo (1.3.1)
28
+ bson (>= 1.3.1)
29
+ mongoid (2.0.2)
30
+ activemodel (~> 3.0)
31
+ mongo (~> 1.3)
32
+ tzinfo (~> 0.3.22)
33
+ multi_json (1.0.3)
34
+ rspec (2.6.0)
35
+ rspec-core (~> 2.6.0)
36
+ rspec-expectations (~> 2.6.0)
37
+ rspec-mocks (~> 2.6.0)
38
+ rspec-core (2.6.3)
39
+ rspec-expectations (2.6.0)
40
+ diff-lcs (~> 1.1.2)
41
+ rspec-mocks (2.6.0)
42
+ tilt (1.3.2)
43
+ tzinfo (0.3.27)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ database_cleaner (~> 0.6.7)
50
+ mocha (~> 0.9.12)
51
+ rspec (~> 2.6.0)
52
+ stratify-base!
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jason Rudolph (http://jasonrudolph.com)
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 NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # stratify-base
2
+
3
+ ## Dependencies
4
+
5
+ Stratify is developed and tested with the following dependencies.
6
+
7
+ * Ruby 1.9.2 (See `.rvmrc` for the specific version.)
8
+ * MongoDB 1.8
9
+
10
+ ## Development
11
+
12
+ To get set up for development on stratify-base, clone the repo, and ...
13
+
14
+ cd stratify/stratify-base
15
+ gem install bundler
16
+ bundle
17
+ rake
18
+
19
+ ## License
20
+
21
+ Copyright 2011 Jason Rudolph ([jasonrudolph.com](http://jasonrudolph.com)). Released under the MIT license. See the LICENSE file for further details.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,33 @@
1
+ require 'mongoid'
2
+ require 'stratify/mongoid_extension'
3
+ require 'stratify/renderable'
4
+
5
+ module Stratify
6
+ class Activity
7
+ include Mongoid::Document
8
+ include Mongoid::Paranoia
9
+ include MongoidExtension::NaturalKey
10
+ include Renderable
11
+
12
+ store_in :activities
13
+
14
+ field :source, :type => String
15
+ field :created_at, :type => DateTime
16
+
17
+ validates_presence_of :created_at
18
+
19
+ def permalink
20
+ nil
21
+ end
22
+
23
+ def created_on
24
+ return nil unless created_at
25
+ created_at.to_date
26
+ end
27
+
28
+ def duplicate?
29
+ duplicate_activities = self.class.where(natural_key_hash)
30
+ duplicate_activities.exists? || duplicate_activities.deleted.exists?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ require 'stratify/logger'
2
+
3
+ module Stratify
4
+ class Archiver
5
+ attr_reader :collector
6
+
7
+ def initialize(collector)
8
+ @collector = collector
9
+ end
10
+
11
+ def run
12
+ collect_activities
13
+ ensure
14
+ record_collection_statistics
15
+ end
16
+
17
+ protected
18
+
19
+ def collect_activities
20
+ collector.activities.each do |activity|
21
+ document_activity_source(activity)
22
+ persist_activity(activity)
23
+ end
24
+ end
25
+
26
+ def document_activity_source(activity)
27
+ activity.source = collector.source
28
+ end
29
+
30
+ def persist_activity(activity)
31
+ # Since we run the collectors frequently, it is very common to encounter
32
+ # objects that we have already imported. If this activity is a duplicate
33
+ # of an existing object, then we skip importing this activity.
34
+ return if activity.duplicate?
35
+
36
+ unless activity.save
37
+ Stratify.logger.error("Failed to persist activity: #{activity}.\nValidation errors: #{activity.errors}")
38
+ end
39
+ end
40
+
41
+ def record_collection_statistics
42
+ collector.update_attribute :last_ran_at, Time.now
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,100 @@
1
+ require 'mongoid'
2
+ require 'stratify/archiver'
3
+ require 'stratify/field_definition'
4
+
5
+ module Stratify
6
+ class Collector
7
+ include Mongoid::Document
8
+
9
+ store_in :collectors
10
+
11
+ field :last_ran_at, :type => DateTime
12
+
13
+ def self.configuration_fields(fields_hash = nil)
14
+ @configuration_fields = initialize_configuration_fields(fields_hash) unless fields_hash.nil?
15
+ @configuration_fields || []
16
+ end
17
+
18
+ def configuration_fields
19
+ self.class.configuration_fields
20
+ end
21
+
22
+ def self.configuration_instructions(instructions = nil)
23
+ @configuration_instructions = instructions unless instructions.nil?
24
+ @configuration_instructions
25
+ end
26
+
27
+ def configuration_instructions
28
+ self.class.configuration_instructions
29
+ end
30
+
31
+ def configuration_summary
32
+ return nil if configuration_fields.empty?
33
+ read_attribute(configuration_fields.first.name)
34
+ end
35
+
36
+ def run
37
+ Archiver.new(self).run
38
+ end
39
+
40
+ # To be implemented by subclasses
41
+ #
42
+ # Returns a collection of activities to be saved.
43
+ def activities
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # Define where this collector's activities originate from (e.g., "Twitter", "iTunes")
48
+ def self.source(src = nil)
49
+ @source = src unless src.nil?
50
+ @source
51
+ end
52
+
53
+ def source
54
+ self.class.source
55
+ end
56
+
57
+ def self.sources
58
+ unsorted_sources = Collector.collector_classes.map(&:source)
59
+ unsorted_sources.sort_by {|source| source.downcase}
60
+ end
61
+
62
+ class << self
63
+ attr_reader :collector_classes
64
+ end
65
+
66
+ @collector_classes = []
67
+
68
+ def self.inherited(subclass)
69
+ super
70
+ Collector.collector_classes << subclass
71
+ end
72
+
73
+ def self.collector_class_for(source)
74
+ Collector.collector_classes.find {|clazz| clazz.source == source}
75
+ end
76
+
77
+ private
78
+
79
+ def self.initialize_configuration_fields(fields_hash)
80
+ field_definitions = objectify_fields(fields_hash)
81
+ apply_fields_to_class(field_definitions)
82
+ field_definitions
83
+ end
84
+
85
+ def self.objectify_fields(fields_hash)
86
+ fields_hash.map do |f|
87
+ name = f[0]
88
+ attributes = f[1]
89
+ Stratify::FieldDefinition.new(name, attributes)
90
+ end
91
+ end
92
+
93
+ def self.apply_fields_to_class(field_definitions)
94
+ field_definitions.each do |field|
95
+ field field.name
96
+ validates_presence_of field.name
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,13 @@
1
+ module Stratify
2
+ class CollectorCoordinator
3
+ def self.run_all
4
+ Stratify::Collector.all.each do |collector|
5
+ begin
6
+ collector.run
7
+ rescue => e
8
+ Stratify.logger.error("Error running collector. Collector => #{collector}. Error => #{e}")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Stratify
2
+ class FieldDefinition
3
+ attr_reader :name, :type, :label
4
+
5
+ def initialize(name, attribute_hash)
6
+ @name = name
7
+ @type = attribute_hash[:type]
8
+ @label = attribute_hash[:label]
9
+ end
10
+
11
+ def label
12
+ @label || @name.to_s.titleize
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Stratify
2
+ class << self
3
+ def logger
4
+ @logger ||= initialize_logger
5
+ end
6
+
7
+ private
8
+
9
+ def initialize_logger
10
+ return Rails.logger if defined?(Rails)
11
+ Logger.new(STDOUT)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,38 @@
1
+ module Stratify
2
+ module MongoidExtension
3
+ module NaturalKey
4
+ module ClassMethods
5
+ attr_reader :natural_key_fields
6
+
7
+ def natural_key(*fields)
8
+ @natural_key_fields = fields.dup
9
+ validates_uniqueness_of_natural_key
10
+ end
11
+
12
+ def validates_uniqueness_of_natural_key
13
+ first, *rest = *natural_key_fields
14
+ if rest.empty?
15
+ validates_uniqueness_of first
16
+ else
17
+ validates_uniqueness_of first, :scope => rest
18
+ end
19
+ end
20
+ end
21
+
22
+ module InstanceMethods
23
+ def natural_key_hash
24
+ {}.tap do |hash|
25
+ self.class.natural_key_fields.each do |field|
26
+ hash[field] = self.send(field)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.included(receiver)
33
+ receiver.extend ClassMethods
34
+ receiver.send :include, InstanceMethods
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ require "tilt"
2
+
3
+ module Stratify
4
+ module Renderable
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def template(data = nil)
11
+ @template = data unless data.nil?
12
+ @template
13
+ end
14
+
15
+ def template_format(format = nil)
16
+ @template_format = format unless format.nil?
17
+ @template_format || default_template_format
18
+ end
19
+
20
+ def default_template_format
21
+ :erb
22
+ end
23
+ end
24
+
25
+ def to_html
26
+ template_handler = Tilt[self.class.template_format].new { self.class.template.strip }
27
+ template_handler.render(presenter)
28
+ end
29
+
30
+ # Returns the object that will be passed to the template for rendering.
31
+ #
32
+ # The default implementation returns 'self' (i.e., the renderable object).
33
+ #
34
+ # Subclasses may optionally override this method to provide an object
35
+ # better suited for use in rendering (e.g., an object implementing the
36
+ # presenter pattern).
37
+ def presenter
38
+ self
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ module Stratify
2
+ module Base
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ require 'stratify/activity'
2
+ require 'stratify/collector'
3
+ require 'stratify/collector_coordinator'
4
+ require 'stratify/version'
data/spec/mongoid.yml ADDED
@@ -0,0 +1,3 @@
1
+ persist_in_safe_mode: true
2
+ host: localhost
3
+ database: stratify_base_test
@@ -0,0 +1,46 @@
1
+ # This file defines an example Stratify collector and activity for testing
2
+ # purposes.
3
+ #
4
+ # Stratify::Bacon::Activity and Stratify::Bacon::Collector represent a
5
+ # (fictitious, unfortunately) service for tracking your bacon-related
6
+ # achievements. This also happens to be an *amazing* idea for a service.
7
+ # You know you want to track your bacon-related achievements. You just know it.
8
+
9
+ # Stratify::Bacon::Activity implements all *mandatory* functionality required
10
+ # for it to be a valid activity.
11
+ module Stratify
12
+ module Bacon
13
+ class Activity < Stratify::Activity
14
+ field :slices, :type => Integer
15
+
16
+ natural_key :created_at
17
+
18
+ template "Enjoyed <%= slices %> slices of delicious bacon"
19
+ end
20
+ end
21
+ end
22
+
23
+ # Stratify::Bacon::Collector implements all *mandatory* functionality required
24
+ # for it to be a valid collector.
25
+ module Stratify
26
+ module Bacon
27
+ class Collector < Stratify::Collector
28
+ source "Baconation"
29
+
30
+ configuration_fields :username => {:type => :string},
31
+ :password => {:type => :password}
32
+
33
+ # Return a fixed set of Stratify::Bacon::Activity objects. (A real
34
+ # collector would tend to return new data as time goes by. For testing
35
+ # purposes, it's convenient to have a fixed set of data returned by the
36
+ # collector.)
37
+ def activities
38
+ [
39
+ Stratify::Bacon::Activity.new(:slices => 3, :created_at => Time.new(2011, 4, 3, 7, 2)),
40
+ Stratify::Bacon::Activity.new(:slices => 5, :created_at => Time.new(2011, 4, 7, 8, 17)),
41
+ Stratify::Bacon::Activity.new(:slices => 4, :created_at => Time.new(2011, 4, 9, 6, 24)),
42
+ ]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'stratify-base'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ require 'database_cleaner'
11
+
12
+ require 'prototypes/bacon'
13
+
14
+ RSpec.configure do |config|
15
+ config.color_enabled = true
16
+ config.formatter = :progress
17
+ config.mock_with :mocha
18
+
19
+ # Configure RSpec to run focused specs, and also respect the alias 'fit' for focused specs
20
+ config.filter_run :focused => true
21
+ config.run_all_when_everything_filtered = true
22
+ config.alias_example_to :fit, :focused => true
23
+
24
+ # Configure RSpec to truncate the database before any spec tagged with ":database => true"
25
+ DatabaseCleaner.strategy = :truncation
26
+ DatabaseCleaner.orm = "mongoid"
27
+ config.before(:each, :database => true) do
28
+ DatabaseCleaner.clean
29
+ end
30
+
31
+ config.before(:suite) do
32
+ silence_mongoid_logging
33
+ initialize_mongoid_configuration
34
+ end
35
+ end
36
+
37
+ def silence_mongoid_logging
38
+ Mongoid.logger = Logger.new("/dev/null")
39
+ end
40
+
41
+ def silence_stratify_logging
42
+ Stratify.stubs(:logger).returns(Logger.new("/dev/null")) # Silence the logger so as not to clutter the test output
43
+ end
44
+
45
+ def initialize_mongoid_configuration
46
+ mongoid_config = YAML::load_file(File.join(File.dirname(__FILE__), "mongoid.yml"))
47
+ Mongoid.from_hash(mongoid_config)
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stratify::Activity do
4
+
5
+ describe "#created_on" do
6
+ it "returns the date portion of the created_at value" do
7
+ activity = Stratify::Activity.new(:created_at => DateTime.parse("2010-11-27 6:35 PM"))
8
+ activity.created_on.should == Date.parse("2010-11-27")
9
+ end
10
+
11
+ it "returns nil if the created_at value is nil" do
12
+ activity = Stratify::Activity.new(:created_at => nil)
13
+ activity.created_on.should be_nil
14
+ end
15
+ end
16
+
17
+ describe "#delete", :database => true do
18
+ it "soft-deletes the activity" do
19
+ creation_time = Time.now
20
+ activity = Stratify::Bacon::Activity.create!(:slices => 42, :created_at => creation_time)
21
+ activity.delete
22
+
23
+ Stratify::Bacon::Activity.where(:slices => 42, :created_at => creation_time).should_not exist
24
+ Stratify::Bacon::Activity.deleted.where(:slices => 42, :created_at => creation_time).should exist
25
+ end
26
+ end
27
+
28
+ describe "#duplicate?", :database => true do
29
+ class ActivityForTestingDuplicates < Stratify::Activity
30
+ field :foo
31
+ field :bar
32
+
33
+ natural_key :foo, :bar
34
+ end
35
+
36
+ it "returns false if no record exists with the same natural key" do
37
+ ActivityForTestingDuplicates.create!(:foo => "a", :bar => "b", :created_at => Time.now)
38
+ ActivityForTestingDuplicates.new(:foo => "1", :bar => "2").should_not be_duplicate
39
+ end
40
+
41
+ it "returns true if a saved record already exists with the same natural key" do
42
+ ActivityForTestingDuplicates.create!(:foo => "a", :bar => "b", :created_at => Time.now)
43
+ ActivityForTestingDuplicates.new(:foo => "a", :bar => "b").should be_duplicate
44
+ end
45
+
46
+ it "returns true if a soft-deleted record already exists with the same natural key" do
47
+ original = ActivityForTestingDuplicates.new(:foo => "a", :bar => "b", :created_at => Time.now)
48
+ original.save!
49
+ original.delete
50
+
51
+ ActivityForTestingDuplicates.new(:foo => "a", :bar => "b").should be_duplicate
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stratify::Archiver do
4
+
5
+ describe ".run" do
6
+ context "when the collection succeeds" do
7
+ it "updates the last_ran_at timestamp for the collector" do
8
+ stub_current_timestamp = Time.parse "2011-04-30 14:11:02"
9
+ Time.stubs(:now).returns stub_current_timestamp
10
+
11
+ collector = Stratify::Collector.create
12
+ archiver = Stratify::Archiver.new(collector)
13
+ archiver.stubs(:collect_activities)
14
+ archiver.run
15
+ collector.last_ran_at.should == stub_current_timestamp
16
+ end
17
+ end
18
+
19
+ context "when an exception occurs during collection" do
20
+ it "propagates the exception to the caller" do
21
+ archiver = Stratify::Archiver.new(collector = stub)
22
+ archiver.stubs(:collect_activities).raises("some gnarly error")
23
+ lambda { archiver.run }.should raise_error
24
+ end
25
+
26
+ it "updates the last_ran_at timestamp for the collector" do
27
+ stub_current_timestamp = Time.parse "2011-04-30 14:11:02"
28
+ Time.stubs(:now).returns stub_current_timestamp
29
+
30
+ collector = Stratify::Collector.create
31
+ archiver = Stratify::Archiver.new(collector)
32
+ archiver.stubs(:collect_activities).raises("some gnarly error")
33
+ archiver.run rescue nil
34
+ collector.last_ran_at.should == stub_current_timestamp
35
+ end
36
+ end
37
+ end
38
+
39
+ describe ".collect_activities" do
40
+ it "documents the source of each activity" do
41
+ example_collector_class = Class.new(Stratify::Collector)
42
+ example_collector_class.source("some-example-source")
43
+ collector = example_collector_class.new
44
+
45
+ activity = Stratify::Activity.new
46
+ collector.stubs(:activities).returns [activity]
47
+
48
+ archiver = Stratify::Archiver.new(collector)
49
+ archiver.stubs(:persist_activity)
50
+ archiver.send(:collect_activities)
51
+ activity.source.should == "some-example-source"
52
+ end
53
+
54
+ it "persists each activity" do
55
+ collector = Stratify::Collector.new
56
+ activity = Stratify::Activity.new
57
+ collector.stubs(:activities).returns [activity]
58
+
59
+ archiver = Stratify::Archiver.new(collector)
60
+ archiver.expects(:persist_activity).with(activity)
61
+ archiver.send(:collect_activities)
62
+ end
63
+ end
64
+
65
+ describe "persisting activities" do
66
+ it "logs when an error is encountered attempting to persist an activity" do
67
+ activity = stub('activity', :duplicate? => false, :save => false, :errors => {})
68
+
69
+ archiver = Stratify::Archiver.new(collector = stub)
70
+
71
+ Stratify.logger.expects(:error).with() { |value| value.starts_with? "Failed to persist activity" }
72
+ archiver.send(:persist_activity, activity)
73
+ end
74
+
75
+ it "does not log an error when activity is persisted successfully" do
76
+ activity = stub('activity', :duplicate? => false, :save => true)
77
+
78
+ archiver = Stratify::Archiver.new(collector = stub)
79
+
80
+ Stratify.logger.expects(:error).never
81
+ archiver.send(:persist_activity, activity)
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stratify::CollectorCoordinator do
4
+
5
+ describe ".run_all" do
6
+ it "runs all collectors" do
7
+ collector_1 = mock(:run)
8
+ collector_2 = mock(:run)
9
+
10
+ Stratify::Collector.stubs(:all).returns([collector_1, collector_2])
11
+
12
+ Stratify::CollectorCoordinator.run_all
13
+ end
14
+
15
+ context "when an exception occurs in a collector" do
16
+ it "logs the exception" do
17
+ collector = stub
18
+ collector.stubs(:run).raises("some gnarly error")
19
+ Stratify::Collector.stubs(:all).returns([collector])
20
+
21
+ Stratify.logger.expects(:error).with() { |value| value.include? "some gnarly error" }
22
+ Stratify::CollectorCoordinator.run_all
23
+ end
24
+
25
+ it "runs the remaining collectors" do
26
+ silence_stratify_logging # Silence the logger so as not to clutter the test output
27
+
28
+ troublesome_collector = stub
29
+ troublesome_collector.stubs(:run).raises("some gnarly error")
30
+
31
+ other_collector = mock
32
+ other_collector.expects(:run).returns(nil)
33
+
34
+ Stratify::Collector.stubs(:all).returns([troublesome_collector, other_collector])
35
+
36
+ Stratify::CollectorCoordinator.run_all
37
+ end
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ class TestCollector < Stratify::Collector
4
+ configuration_fields :username => {:type => :string},
5
+ :password => {:type => :password}
6
+ end
7
+
8
+ def anonymous_collector_subclass
9
+ Class.new(Stratify::Collector)
10
+ end
11
+
12
+ describe Stratify::Collector do
13
+
14
+ describe ".collector_class_for" do
15
+ it "returns the collector subclass associated with the given source" do
16
+ example_collector_subclass = anonymous_collector_subclass
17
+ example_collector_subclass.source("Twitter")
18
+ Stratify::Collector.collector_class_for("Twitter").should == example_collector_subclass
19
+ end
20
+
21
+ it "returns nil when no collector subclass exists for the given source" do
22
+ Stratify::Collector.collector_class_for("lol").should be_nil
23
+ end
24
+ end
25
+
26
+ describe ".sources" do
27
+ before { Stratify::Collector.collector_classes.clear }
28
+
29
+ it "returns the list of sources for all collectors" do
30
+ anonymous_collector_subclass.source("FourSquare")
31
+ anonymous_collector_subclass.source("Twitter")
32
+ Stratify::Collector.sources.should =~ ["FourSquare", "Twitter"]
33
+ end
34
+
35
+ it "returns the sources in case-insensitive alphabetical order" do
36
+ anonymous_collector_subclass.source("Twitter")
37
+ anonymous_collector_subclass.source("iTunes")
38
+ anonymous_collector_subclass.source("Instapaper")
39
+ anonymous_collector_subclass.source("Pandora")
40
+ Stratify::Collector.sources.should == %w(Instapaper iTunes Pandora Twitter)
41
+ end
42
+ end
43
+
44
+ describe ".configuration_fields" do
45
+ it "tracks the fields used to configure the collector" do
46
+ fields = TestCollector.configuration_fields
47
+ fields.find {|f| f.name == :username && f.type == :string}.should be
48
+ fields.find {|f| f.name == :password && f.type == :password}.should be
49
+ end
50
+
51
+ it "registers the given fields with Mongoid" do
52
+ TestCollector.fields.should include("username", "password")
53
+ end
54
+
55
+ describe "influences validation" do
56
+ it "adds an error when a collector instance fails to provide a value for each field" do
57
+ collector = TestCollector.new
58
+ collector.should_not be_valid
59
+ collector.errors[:username].should include("can't be blank")
60
+ end
61
+
62
+ it "adds NO errors when a collector instance provides a value for each field" do
63
+ collector = TestCollector.new(:username => "foo")
64
+ collector.valid?
65
+ collector.errors[:username].should_not include("can't be blank")
66
+ end
67
+ end
68
+ end
69
+
70
+ describe ".configuration_instructions (class method)" do
71
+ it "returns the configuration instructions for a specific Collector subclass" do
72
+ collector_subclass = anonymous_collector_subclass
73
+ collector_subclass.configuration_instructions("Provide your username and ...")
74
+ collector_subclass.configuration_instructions.should == "Provide your username and ..."
75
+ end
76
+
77
+ it "returns nil if a Collector subclass does not specify any configuration instructions" do
78
+ anonymous_collector_subclass.configuration_instructions.should == nil
79
+ end
80
+ end
81
+
82
+ describe "#configuration_instructions (instance method)" do
83
+ it "returns the configuration instructions defined in the class" do
84
+ collector_subclass = anonymous_collector_subclass
85
+ collector_subclass.configuration_instructions("Provide your username and ...")
86
+ collector = collector_subclass.new
87
+ collector.configuration_instructions.should == "Provide your username and ..."
88
+ end
89
+ end
90
+
91
+ describe "#configuration_summary" do
92
+ context "when the collector's class has one configuration field" do
93
+ it "returns the value of the field" do
94
+ collector_subclass = Class.new(Stratify::Collector) do
95
+ configuration_fields :username => {:type => :string}
96
+ end
97
+ collector = collector_subclass.new
98
+ collector.username = "johndoe"
99
+ collector.configuration_summary.should == "johndoe"
100
+ end
101
+ end
102
+
103
+ context "when the collector's class has multiple configuration fields" do
104
+ it "returns the value of the first field" do
105
+ collector_subclass = Class.new(Stratify::Collector) do
106
+ configuration_fields :username => {:type => :string},
107
+ :tag => {:type => :string}
108
+ end
109
+ collector = collector_subclass.new
110
+ collector.username = "johndoe"
111
+ collector.tag = "programming"
112
+ collector.configuration_summary.should == "johndoe"
113
+ end
114
+ end
115
+
116
+ context "when the collector's class has no configuration fields" do
117
+ it "returns nil" do
118
+ collector_subclass = Class.new(Stratify::Collector)
119
+ collector = collector_subclass.new
120
+ collector.configuration_summary.should == nil
121
+ end
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stratify::FieldDefinition do
4
+ describe "#name" do
5
+ it "returns the name of the field" do
6
+ field = Stratify::FieldDefinition.new(:username, :type => :string)
7
+ field.name.should == :username
8
+ end
9
+ end
10
+
11
+ describe "#type" do
12
+ it "returns the type of the field" do
13
+ field = Stratify::FieldDefinition.new(:username, :type => :string)
14
+ field.type.should == :string
15
+ end
16
+ end
17
+
18
+ describe "#label" do
19
+ it "returns the label for the field when an explicit label is specified" do
20
+ field = Stratify::FieldDefinition.new(:username, :type => :string, :label => "User ID")
21
+ field.label.should == "User ID"
22
+ end
23
+
24
+ it "returns the titleized name for the field when no explicit label is specified" do
25
+ field = Stratify::FieldDefinition.new(:username, :type => :string)
26
+ field.label.should == "Username"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "collecting activities", :database => true do
4
+ it "runs the collector and stores activities" do
5
+ collector = Stratify::Bacon::Collector.create!(:username => "johndoe", :password => "secret")
6
+
7
+ expect { collector.run }.to change(Stratify::Bacon::Activity, :count).by(3)
8
+
9
+ Stratify::Bacon::Activity.where(:slices => 3, :created_at => Time.new(2011, 4, 3, 7, 2)).should exist
10
+ Stratify::Bacon::Activity.where(:slices => 5, :created_at => Time.new(2011, 4, 7, 8, 17)).should exist
11
+ Stratify::Bacon::Activity.where(:slices => 4, :created_at => Time.new(2011, 4, 9, 6, 24)).should exist
12
+ end
13
+
14
+ it "does not import duplicate activities" do
15
+ collector = Stratify::Bacon::Collector.new(:username => "johndoe", :password => "secret")
16
+ expect { collector.run }.to change(Stratify::Bacon::Activity, :count).by(3)
17
+ expect { collector.run }.to change(Stratify::Bacon::Activity, :count).by(0)
18
+ end
19
+
20
+ it "does not re-import soft-deleted activities" do
21
+ collector = Stratify::Bacon::Collector.new(:username => "johndoe", :password => "secret")
22
+
23
+ collector.run
24
+ activity = Stratify::Bacon::Activity.where(:created_at => Time.new(2011, 4, 3, 7, 2)).first
25
+ activity.should be
26
+ activity.delete
27
+
28
+ collector.run
29
+ Stratify::Bacon::Activity.where(:created_at => Time.new(2011, 4, 3, 7, 2)).should_not exist
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ def class_with_natural_key_mixin(&blk)
4
+ Class.new do
5
+ include Mongoid::Document
6
+ include Stratify::MongoidExtension::NaturalKey
7
+ yield(self) if block_given?
8
+ end
9
+ end
10
+
11
+ describe Stratify::MongoidExtension::NaturalKey do
12
+ describe ".natural_key" do
13
+ context "given a single field as the key" do
14
+ it "stores the field in an array of key fields" do
15
+ klass = class_with_natural_key_mixin do |k|
16
+ k.natural_key :status_id
17
+ end
18
+ klass.natural_key_fields.should == [:status_id]
19
+ end
20
+ end
21
+
22
+ context "given a multi-field key" do
23
+ it "stores the fields in an array of key fields" do
24
+ klass = class_with_natural_key_mixin do |k|
25
+ k.natural_key :url, :created_at
26
+ end
27
+ klass.natural_key_fields.should == [:url, :created_at]
28
+ end
29
+ end
30
+
31
+ it "declares a uniqueness validator for the natural key" do
32
+ klass = class_with_natural_key_mixin
33
+ klass.expects(:validates_uniqueness_of_natural_key)
34
+ klass.natural_key :foo
35
+ end
36
+ end
37
+
38
+ describe ".validates_uniqueness_of_natural_key" do
39
+ context "given a single field as the key" do
40
+ it "declares that no two documents can have the same value for that field" do
41
+ klass = class_with_natural_key_mixin
42
+ klass.stubs(:natural_key_fields).returns [:status_id]
43
+ klass.expects(:validates_uniqueness_of).with(:status_id)
44
+ klass.validates_uniqueness_of_natural_key
45
+ end
46
+ end
47
+
48
+ context "given a two-field key" do
49
+ it "declares that no two documents can have the same combination of values for these two fields" do
50
+ klass = class_with_natural_key_mixin
51
+ klass.stubs(:natural_key_fields).returns [:url, :created_at]
52
+ klass.expects(:validates_uniqueness_of).with(:url, :scope => [:created_at])
53
+ klass.validates_uniqueness_of_natural_key
54
+ end
55
+ end
56
+
57
+ context "given a key with more than two fields" do
58
+ it "declares that no two documents can have the same combination of values for these fields" do
59
+ klass = class_with_natural_key_mixin
60
+ klass.stubs(:natural_key_fields).returns [:foo, :bar, :baz]
61
+ klass.expects(:validates_uniqueness_of).with(:foo, :scope => [:bar, :baz])
62
+ klass.validates_uniqueness_of_natural_key
63
+ end
64
+ end
65
+ end
66
+
67
+ describe "#natural_key_hash" do
68
+ it "returns the field name and value for the object's natural key" do
69
+ klass = class_with_natural_key_mixin do |k|
70
+ k.field :foo
71
+ k.field :bar
72
+ k.field :baz
73
+ k.natural_key :foo, :bar
74
+ end
75
+
76
+ model = klass.new(:foo => 1, :bar => 2, :baz => 3)
77
+ model.natural_key_hash.should == {:foo => 1, :bar => 2}
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ def anonymous_class_with_renderable_mixin
4
+ Class.new do
5
+ include Stratify::Renderable
6
+ end
7
+ end
8
+
9
+ describe Stratify::Renderable do
10
+ describe ".template" do
11
+ it "returns the template specified for use in rendering the object" do
12
+ renderable_class = anonymous_class_with_renderable_mixin
13
+ renderable_class.template "some custom template"
14
+ renderable_class.template.should == "some custom template"
15
+ end
16
+ end
17
+
18
+ describe ".template_format" do
19
+ it "returns ':erb' when no explicit format has been set" do
20
+ renderable_class = anonymous_class_with_renderable_mixin
21
+ renderable_class.template_format.should == :erb
22
+ end
23
+
24
+ it "returns a custom template format when one has been set" do
25
+ renderable_class = anonymous_class_with_renderable_mixin
26
+ renderable_class.template_format :haml
27
+ renderable_class.template_format.should == :haml
28
+ end
29
+ end
30
+
31
+ describe "#presenter" do
32
+ it "returns the object" do
33
+ renderable_object = anonymous_class_with_renderable_mixin.new
34
+ renderable_object.presenter.should == renderable_object
35
+ end
36
+ end
37
+
38
+ describe "#to_html" do
39
+ it "returns the result of rendering the template with the presenter" do
40
+ renderable_class = anonymous_class_with_renderable_mixin
41
+ renderable_class.template "Hello, <%= name %>"
42
+
43
+ renderable_object = renderable_class.new
44
+ class << renderable_object
45
+ def name
46
+ "Kilgore"
47
+ end
48
+ end
49
+
50
+ renderable_object.to_html.should == "Hello, Kilgore"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "stratify/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "stratify-base"
7
+ s.version = Stratify::Base::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jason Rudolph"]
10
+ s.email = ["jason@jasonrudolph.com"]
11
+ s.homepage = "http://github.com/jasonrudolph/stratify/"
12
+ s.summary = "Core collector and activity componentry for Stratify"
13
+ s.description = "Provides the infrastructure to support the development and use of Stratify collectors and activities"
14
+
15
+ s.rubyforge_project = "stratify-base"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_runtime_dependency "bson_ext", "~> 1.3.1"
23
+ s.add_runtime_dependency "mongoid", "~> 2.0.2"
24
+ s.add_runtime_dependency "tilt", "~> 1.3.2"
25
+
26
+ s.add_development_dependency "rspec", "~> 2.6.0"
27
+ s.add_development_dependency "mocha", "~> 0.9.12"
28
+ s.add_development_dependency "database_cleaner", "~> 0.6.7"
29
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stratify-base
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Jason Rudolph
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-26 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bson_ext
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 1.3.1
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: mongoid
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 2.0.2
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: tilt
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.2
46
+ type: :runtime
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: 2.6.0
57
+ type: :development
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: mocha
61
+ prerelease: false
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: 0.9.12
68
+ type: :development
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: database_cleaner
72
+ prerelease: false
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 0.6.7
79
+ type: :development
80
+ version_requirements: *id006
81
+ description: Provides the infrastructure to support the development and use of Stratify collectors and activities
82
+ email:
83
+ - jason@jasonrudolph.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files: []
89
+
90
+ files:
91
+ - .gitignore
92
+ - .rvmrc
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE
96
+ - README.md
97
+ - Rakefile
98
+ - lib/stratify-base.rb
99
+ - lib/stratify/activity.rb
100
+ - lib/stratify/archiver.rb
101
+ - lib/stratify/collector.rb
102
+ - lib/stratify/collector_coordinator.rb
103
+ - lib/stratify/field_definition.rb
104
+ - lib/stratify/logger.rb
105
+ - lib/stratify/mongoid_extension.rb
106
+ - lib/stratify/renderable.rb
107
+ - lib/stratify/version.rb
108
+ - spec/mongoid.yml
109
+ - spec/prototypes/bacon.rb
110
+ - spec/spec_helper.rb
111
+ - spec/stratify/activity_spec.rb
112
+ - spec/stratify/archiver_spec.rb
113
+ - spec/stratify/collector_coordinator_spec.rb
114
+ - spec/stratify/collector_spec.rb
115
+ - spec/stratify/field_definition_spec.rb
116
+ - spec/stratify/integration_spec.rb
117
+ - spec/stratify/mongoid_extension_spec.rb
118
+ - spec/stratify/renderable_spec.rb
119
+ - stratify-base.gemspec
120
+ homepage: http://github.com/jasonrudolph/stratify/
121
+ licenses: []
122
+
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: "0"
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: "0"
140
+ requirements: []
141
+
142
+ rubyforge_project: stratify-base
143
+ rubygems_version: 1.8.5
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: Core collector and activity componentry for Stratify
147
+ test_files:
148
+ - spec/mongoid.yml
149
+ - spec/prototypes/bacon.rb
150
+ - spec/spec_helper.rb
151
+ - spec/stratify/activity_spec.rb
152
+ - spec/stratify/archiver_spec.rb
153
+ - spec/stratify/collector_coordinator_spec.rb
154
+ - spec/stratify/collector_spec.rb
155
+ - spec/stratify/field_definition_spec.rb
156
+ - spec/stratify/integration_spec.rb
157
+ - spec/stratify/mongoid_extension_spec.rb
158
+ - spec/stratify/renderable_spec.rb