stratify-base 0.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.
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