publishing_logic 0.1.3 → 0.2.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 +2 -1
- data/README.rdoc +6 -4
- data/Rakefile +6 -11
- data/VERSION +1 -1
- data/lib/model_logic.rb +67 -26
- data/rails_generators/publishing_logic_fields/publishing_logic_fields_generator.rb +21 -2
- data/rails_generators/publishing_logic_fields/templates/app/views/publishing_logic_fields.html.erb +2 -2
- data/rails_generators/publishing_logic_fields/templates/db/migrate/add_publishing_logic_fields.rb.erb +9 -10
- data/spec/general_model_logic.rb +93 -0
- data/spec/models_with_all_fields_spec.rb +85 -0
- data/spec/models_with_no_published_until_field_spec.rb +68 -0
- data/spec/spec_helper.rb +45 -8
- data/spec/support/database_cleanliness.rb +14 -0
- data/spec/support/database_connection.rb +4 -0
- data/spec/support/database_migrations.rb +56 -0
- data/spec/support/factories.rb +10 -0
- data/spec/support/logging.rb +2 -0
- data/spec/support/models/article.rb +3 -0
- data/spec/support/models/programme.rb +3 -0
- metadata +135 -20
- data/features/publishing_logic.feature +0 -9
- data/features/step_definitions/publishing_logic_steps.rb +0 -0
- data/features/support/env.rb +0 -4
- data/spec/lib/model_logic_spec.rb +0 -158
- data/spec/publishing_logic_spec.rb +0 -4
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -4,12 +4,14 @@ Publishing logic for ActiveRecord models
|
|
4
4
|
* Generates a migration with fields required for publishing
|
5
5
|
logic. Fields are:
|
6
6
|
* publishing_enabled:boolean
|
7
|
-
*
|
8
|
-
*
|
7
|
+
* published_at:datetime
|
8
|
+
* published_until:datetime (optional)
|
9
9
|
* Defines logic on models that include PublishingLogic::ModelLogic
|
10
10
|
* published named scope
|
11
11
|
* published? method
|
12
|
-
* ordering by
|
12
|
+
* ordering by published_at
|
13
|
+
|
14
|
+
The source for this gem can be found on http://github.com/unboxed/publishing_logic
|
13
15
|
|
14
16
|
== Note on Patches/Pull Requests
|
15
17
|
|
@@ -24,4 +26,4 @@ Publishing logic for ActiveRecord models
|
|
24
26
|
|
25
27
|
== Copyright
|
26
28
|
|
27
|
-
Copyright (c) 2009 Unboxed Consulting. See LICENSE for details.
|
29
|
+
Copyright (c) 2009 Unboxed Consulting and Channel 5 Broadcasting Ltd. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -12,6 +12,12 @@ begin
|
|
12
12
|
gem.authors = ["Unboxed Consulting"]
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
gem.add_development_dependency "cucumber", ">= 0"
|
15
|
+
gem.add_development_dependency 'rspec-rails', ">= 1.3.0"
|
16
|
+
gem.add_development_dependency 'factory_girl', ">= 1.2.0"
|
17
|
+
gem.add_development_dependency 'activesupport', ">= 2.3.0"
|
18
|
+
gem.add_development_dependency 'activerecord', ">= 2.3.0"
|
19
|
+
gem.add_development_dependency 'database_cleaner', ">= 0.5.0"
|
20
|
+
|
15
21
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
22
|
end
|
17
23
|
Jeweler::GemcutterTasks.new
|
@@ -33,17 +39,6 @@ end
|
|
33
39
|
|
34
40
|
task :spec => :check_dependencies
|
35
41
|
|
36
|
-
begin
|
37
|
-
require 'cucumber/rake/task'
|
38
|
-
Cucumber::Rake::Task.new(:features)
|
39
|
-
|
40
|
-
task :features => :check_dependencies
|
41
|
-
rescue LoadError
|
42
|
-
task :features do
|
43
|
-
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
42
|
task :default => :spec
|
48
43
|
|
49
44
|
require 'rake/rdoctask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/model_logic.rb
CHANGED
@@ -1,41 +1,82 @@
|
|
1
1
|
module PublishingLogic
|
2
2
|
module ModelLogic
|
3
3
|
|
4
|
-
def published?
|
5
|
-
return false if published_at && Time.now < published_at
|
6
|
-
return false if published_until && Time.now > published_until
|
7
|
-
publishing_enabled?
|
8
|
-
end
|
9
|
-
|
10
4
|
def self.included(base)
|
11
|
-
base.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# TODO Not using table name with the following methods so in danger of getting ambiguous column names
|
19
|
-
def newest
|
20
|
-
find(:first, :order => "published_at DESC")
|
21
|
-
end
|
22
|
-
|
23
|
-
def oldest
|
24
|
-
find(:last, :order => "published_at DESC")
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
5
|
+
module_to_include = if base.column_names.include?('published_until')
|
6
|
+
WithPublishedUntilField
|
7
|
+
else
|
8
|
+
WithoutPublishedUntilField
|
9
|
+
end
|
10
|
+
base.class_eval do
|
28
11
|
# If objects have identical published_at values, order by created_at. If these are
|
29
12
|
# identical as well, then order by id. This is done to ensure there is a unique
|
30
13
|
# ordering of objects, ordering by newest and oldest should result in arrays that are
|
31
14
|
# the inverse of the other.
|
32
|
-
named_scope :
|
33
|
-
named_scope :
|
15
|
+
named_scope :by_publication_date_oldest_first, :order => "#{base.table_name}.published_at ASC, #{base.table_name}.created_at ASC, #{base.table_name}.id ASC"
|
16
|
+
named_scope :by_publication_date_newest_first, :order => "#{base.table_name}.published_at DESC, #{base.table_name}.created_at DESC, #{base.table_name}.id DESC"
|
17
|
+
|
18
|
+
include module_to_include
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def by_date_oldest_first
|
22
|
+
::ActiveSupport::Deprecation.warn("by_date_oldest_first is deprecated and will be removed in the next version of this gem (use by_publication_date_oldest_first instead, it's more intention revealing).", caller)
|
23
|
+
by_publication_date_oldest_first
|
24
|
+
end
|
25
|
+
|
26
|
+
def by_date_newest_first
|
27
|
+
::ActiveSupport::Deprecation.warn("by_date_newest_first is deprecated and will be removed in the next version of this gem (use by_publication_date_newest_first instead, it's more intention revealing).", caller)
|
28
|
+
by_publication_date_newest_first
|
29
|
+
end
|
30
|
+
end
|
34
31
|
end
|
35
32
|
end
|
36
33
|
|
37
|
-
module
|
34
|
+
module WithoutPublishedUntilField
|
35
|
+
def self.included(base)
|
36
|
+
base.class_eval do
|
37
|
+
named_scope :published, lambda {{ :conditions => ["#{base.table_name}.publishing_enabled = ? AND \
|
38
|
+
(#{base.table_name}.published_at is null or #{base.table_name}.published_at < ?)",
|
39
|
+
true, Time.now.utc] }} do
|
40
|
+
def newest
|
41
|
+
find(:first, :order => "#{proxy_scope.table_name}.published_at DESC")
|
42
|
+
end
|
43
|
+
|
44
|
+
def oldest
|
45
|
+
find(:last, :order => "#{proxy_scope.table_name}.published_at DESC")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def published?
|
50
|
+
return false if published_at && Time.now < published_at
|
51
|
+
publishing_enabled?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
38
55
|
end
|
39
56
|
|
57
|
+
module WithPublishedUntilField
|
58
|
+
def self.included(base)
|
59
|
+
base.class_eval do
|
60
|
+
named_scope :published, lambda {{ :conditions => ["#{base.table_name}.publishing_enabled = ? AND \
|
61
|
+
(#{base.table_name}.published_until is null or #{base.table_name}.published_until > ?) AND \
|
62
|
+
(#{base.table_name}.published_at is null or #{base.table_name}.published_at < ?)",
|
63
|
+
true, Time.now.utc, Time.now.utc] }} do
|
64
|
+
def newest
|
65
|
+
find(:first, :order => "#{proxy_scope.table_name}.published_at DESC")
|
66
|
+
end
|
67
|
+
|
68
|
+
def oldest
|
69
|
+
find(:last, :order => "#{proxy_scope.table_name}.published_at DESC")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def published?
|
74
|
+
return false if published_at && Time.now < published_at
|
75
|
+
return false if published_until && Time.now > published_until
|
76
|
+
publishing_enabled?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
40
81
|
end
|
41
82
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class PublishingLogicFieldsGenerator < Rails::Generator::NamedBase
|
2
|
+
default_options :use_published_until_field => true, :skip_admin_form => false
|
3
|
+
|
2
4
|
def initialize(args, options)
|
3
5
|
super(args, options)
|
4
6
|
end
|
@@ -7,9 +9,26 @@ class PublishingLogicFieldsGenerator < Rails::Generator::NamedBase
|
|
7
9
|
record do |m|
|
8
10
|
class_name.camelize.constantize # Raise an error if model does not yet exist
|
9
11
|
m.migration_template 'db/migrate/add_publishing_logic_fields.rb.erb', 'db/migrate', :assigns => {
|
10
|
-
:migration_name => "AddPublishingLogicFieldsTo#{class_name.pluralize.gsub(/::/, '')}"
|
12
|
+
:migration_name => "AddPublishingLogicFieldsTo#{class_name.pluralize.gsub(/::/, '')}",
|
13
|
+
:use_published_until_field => options[:use_published_until_field]
|
11
14
|
}, :migration_file_name => "add_publishing_logic_fields_to_#{file_path.gsub(/\//, '_').pluralize}"
|
12
|
-
|
15
|
+
unless options[:skip_admin_form]
|
16
|
+
m.template 'app/views/publishing_logic_fields.html.erb',
|
17
|
+
File.join('app', 'views', 'admin', plural_name, "_publishing_logic_fields.html.erb"),
|
18
|
+
:assigns => {
|
19
|
+
:use_published_until_field => options[:use_published_until_field]
|
20
|
+
}
|
21
|
+
end
|
13
22
|
end
|
14
23
|
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def add_options!(opt)
|
27
|
+
opt.separator ''
|
28
|
+
opt.separator 'Options:'
|
29
|
+
opt.on("--no-published-until",
|
30
|
+
"Don't add the published_until field to this model") { |v| options[:use_published_until_field] = false }
|
31
|
+
opt.on("--skip-admin-form",
|
32
|
+
"Don't generate the admin form partial for this model") { |v| options[:skip_admin_form] = true }
|
33
|
+
end
|
15
34
|
end
|
data/rails_generators/publishing_logic_fields/templates/app/views/publishing_logic_fields.html.erb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
<%%= form.label :published_at %><br />
|
3
3
|
<%%= form.datetime_select :published_at %>
|
4
4
|
</p>
|
5
|
-
|
5
|
+
<% if use_published_until_field %><p>
|
6
6
|
<%%= form.label :published_until %><br />
|
7
7
|
<%%= form.datetime_select :published_until %>
|
8
|
-
</p
|
8
|
+
</p><% end %>
|
9
9
|
<p>
|
10
10
|
<%%= form.label :publishing_enabled %>
|
11
11
|
<%%= form.check_box :publishing_enabled %>
|
@@ -2,18 +2,17 @@
|
|
2
2
|
|
3
3
|
class <%= migration_name %> < ActiveRecord::Migration
|
4
4
|
def self.up
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
change_table :<%= table_name %> do |t|
|
6
|
+
t.boolean :publishing_enabled
|
7
|
+
t.datetime :published_at<% if use_published_until_field %>, :published_until<% end %>
|
8
|
+
t.index [:published_at, :publishing_enabled<% if use_published_until_field %>, :published_until<% end %>], :name => 'index_<%= table_name %>_on_publishing_logic_fields'
|
9
|
+
end
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.down
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
remove_index :<%= table_name %>, :name => :index_<%= table_name %>_on_publishing_logic_fields
|
13
|
+
change_table :<%= table_name %> do |t|
|
14
|
+
t.remove :publishing_enabled, :published_at<% if use_published_until_field %>, :published_until<% end %>
|
15
|
+
t.remove_index :name => 'index_<%= table_name %>_on_publishing_logic_fields'
|
16
|
+
end
|
18
17
|
end
|
19
18
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
shared_examples_for 'a model with publish logic' do
|
2
|
+
def create_objects_with_different_published_at_dates(for_class)
|
3
|
+
factory_name = for_class.name.underscore.to_sym
|
4
|
+
@object2 = Factory.create(factory_name, :publishing_enabled => true, :published_at => 2.days.ago)
|
5
|
+
@object1 = Factory.create(factory_name, :publishing_enabled => true, :published_at => 1.days.ago)
|
6
|
+
@object3 = Factory.create(factory_name, :publishing_enabled => true, :published_at => 3.days.ago)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "ordering by published_at" do
|
10
|
+
describe "from the published scope" do
|
11
|
+
before do
|
12
|
+
create_objects_with_different_published_at_dates(@class)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "newest first" do
|
16
|
+
it "should be the most recently published object" do
|
17
|
+
@class.published.newest.should == @object1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "oldest first" do
|
22
|
+
it "should be the object published the longest ago" do
|
23
|
+
@class.published.oldest.should == @object3
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "by publication date oldest first" do
|
29
|
+
before do
|
30
|
+
create_objects_with_different_published_at_dates(@class)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return the items, oldest first" do
|
34
|
+
@class.by_publication_date_oldest_first.map(&:id).should == [@object3,
|
35
|
+
@object2,
|
36
|
+
@object1].map(&:id)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should order by created_at date if published_ats are equal" do
|
40
|
+
@object2b = Factory.create(@class.name.underscore.to_sym,
|
41
|
+
:publishing_enabled => true,
|
42
|
+
:published_at => @object2.published_at,
|
43
|
+
:created_at => 3.days.ago)
|
44
|
+
@class.by_publication_date_oldest_first.map(&:id).should == [@object3,
|
45
|
+
@object2b,
|
46
|
+
@object2,
|
47
|
+
@object1].map(&:id)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should have deprecated by_date_oldest_first' do
|
51
|
+
assert_deprecated do
|
52
|
+
@class.by_date_oldest_first
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "by publication date newest first" do
|
58
|
+
before do
|
59
|
+
create_objects_with_different_published_at_dates(@class)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should return the items, newest first" do
|
63
|
+
@class.by_publication_date_newest_first.map(&:id).should == [@object1,
|
64
|
+
@object2,
|
65
|
+
@object3].map(&:id)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should order by created_at date if published_ats are equal" do
|
69
|
+
@object2b = Factory.create(@class.name.underscore.to_sym,
|
70
|
+
:publishing_enabled => true,
|
71
|
+
:published_at => @object2.published_at,
|
72
|
+
:created_at => 3.days.from_now)
|
73
|
+
@class.by_publication_date_newest_first.map(&:id).should == [@object1,
|
74
|
+
@object2b,
|
75
|
+
@object2,
|
76
|
+
@object3].map(&:id)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should have deprecated by_date_newest_first' do
|
80
|
+
assert_deprecated do
|
81
|
+
@class.by_date_newest_first
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should have a newest first ordering that is the reverse of the oldest first ordering for identical objects" do
|
87
|
+
creation_time = 2.days.ago
|
88
|
+
publish_time = 1.days.ago
|
89
|
+
5.times { Factory.create(@class.name.underscore.to_sym, :created_at => creation_time, :published_at => publish_time) }
|
90
|
+
@class.by_publication_date_newest_first.map(&:id).should == @class.by_publication_date_oldest_first.map(&:id).reverse
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require 'general_model_logic'
|
3
|
+
|
4
|
+
describe 'Using publishing logic on models with all fields' do
|
5
|
+
describe "published?" do
|
6
|
+
describe "with publishing enabled" do
|
7
|
+
it "should be published by default" do
|
8
|
+
Factory.create(:programme,
|
9
|
+
:publishing_enabled => true,
|
10
|
+
:published_at => nil,
|
11
|
+
:published_until => nil).should be_published
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not be published if the published_at datetime is in the future" do
|
15
|
+
Factory.create(:programme,
|
16
|
+
:publishing_enabled => true,
|
17
|
+
:published_at => 5.seconds.from_now,
|
18
|
+
:published_until => nil).should_not be_published
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not be published if the published_until datetime is in the past" do
|
22
|
+
Factory.create(:programme,
|
23
|
+
:publishing_enabled => true,
|
24
|
+
:published_at => nil,
|
25
|
+
:published_until => 5.seconds.ago).should_not be_published
|
26
|
+
end
|
27
|
+
end
|
28
|
+
describe "with publishing disabled" do
|
29
|
+
it "should not be published" do
|
30
|
+
Factory.create(:programme,
|
31
|
+
:publishing_enabled => false,
|
32
|
+
:published_at => 1.days.ago,
|
33
|
+
:published_until => 10.days.from_now).should_not be_published
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "published named scope" do
|
39
|
+
it "should include published objects" do
|
40
|
+
programme = Factory.create(:programme, :publishing_enabled => true)
|
41
|
+
Programme.published.should == [programme]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not include any unpublished objects" do
|
45
|
+
Factory.create(:programme, :publishing_enabled => false)
|
46
|
+
Programme.published.should be_empty
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not include objects with a published_until in the past" do
|
50
|
+
Factory.create(:programme,
|
51
|
+
:publishing_enabled => true,
|
52
|
+
:published_until => 5.seconds.ago)
|
53
|
+
Programme.published.should be_empty
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not include objects with a published_at in the future" do
|
57
|
+
Factory.create(:programme,
|
58
|
+
:publishing_enabled => true,
|
59
|
+
:published_at => 5.seconds.from_now)
|
60
|
+
Programme.published.should be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should get a new Time.now for each invocation of the named scope" do
|
64
|
+
mock_now = mock('now', :utc => 20.days.from_now, :to_f => 0)
|
65
|
+
Time.stub(:now).and_return mock_now
|
66
|
+
Programme.published
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should use the utc of the current time" do
|
70
|
+
# Make sure utc is used, which is hard to test as a behaviour
|
71
|
+
mock_now = mock('now')
|
72
|
+
Time.stub(:now).and_return mock_now
|
73
|
+
mock_now.should_receive(:utc).twice
|
74
|
+
Programme.published
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'generally' do
|
79
|
+
before do
|
80
|
+
@class = Programme
|
81
|
+
end
|
82
|
+
|
83
|
+
it_should_behave_like 'a model with publish logic'
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
require 'general_model_logic'
|
3
|
+
|
4
|
+
describe 'Using publishing logic on models with no published until field' do
|
5
|
+
describe "published?" do
|
6
|
+
describe "with publishing enabled" do
|
7
|
+
it "should be published by default" do
|
8
|
+
Factory.create(:article,
|
9
|
+
:publishing_enabled => true,
|
10
|
+
:published_at => nil).should be_published
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not be published if the published_at datetime is in the future" do
|
14
|
+
Factory.create(:article,
|
15
|
+
:publishing_enabled => true,
|
16
|
+
:published_at => 5.seconds.from_now).should_not be_published
|
17
|
+
end
|
18
|
+
end
|
19
|
+
describe "with publishing disabled" do
|
20
|
+
it "should not be published" do
|
21
|
+
Factory.create(:article,
|
22
|
+
:publishing_enabled => false,
|
23
|
+
:published_at => 1.days.ago).should_not be_published
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "published named scope" do
|
29
|
+
it "should include published objects" do
|
30
|
+
article = Factory.create(:article, :publishing_enabled => true)
|
31
|
+
Article.published.should == [article]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not include any unpublished objects" do
|
35
|
+
Factory.create(:article, :publishing_enabled => false)
|
36
|
+
Article.published.should be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not include objects with a published_at in the future" do
|
40
|
+
Factory.create(:article,
|
41
|
+
:publishing_enabled => true,
|
42
|
+
:published_at => 5.seconds.from_now)
|
43
|
+
Article.published.should be_empty
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should get a new Time.now for each invocation of the named scope" do
|
47
|
+
mock_now = mock('now', :utc => 20.days.from_now, :to_f => 0)
|
48
|
+
Time.stub(:now).and_return mock_now
|
49
|
+
Article.published
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should use the utc of the current time" do
|
53
|
+
# Make sure utc is used, which is hard to test as a behaviour
|
54
|
+
mock_now = mock('now')
|
55
|
+
Time.stub(:now).and_return mock_now
|
56
|
+
mock_now.should_receive(:utc).once
|
57
|
+
Article.published
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'generally' do
|
62
|
+
before do
|
63
|
+
@class = Article
|
64
|
+
end
|
65
|
+
|
66
|
+
it_should_behave_like 'a model with publish logic'
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,18 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec'
|
3
|
+
gem 'rspec-rails'
|
4
|
+
gem 'factory_girl'
|
5
|
+
gem 'activesupport'
|
6
|
+
gem 'activerecord'
|
7
|
+
gem 'database_cleaner'
|
6
8
|
|
7
9
|
require 'factory_girl'
|
10
|
+
require 'active_support'
|
11
|
+
require 'active_record'
|
12
|
+
|
13
|
+
# We don't need all of spec/rails - just the bits that're to do with ActiveRecord
|
14
|
+
|
15
|
+
require 'active_support/test_case'
|
16
|
+
require 'active_record/fixtures' if defined?(ActiveRecord::Base)
|
17
|
+
|
18
|
+
require 'spec/test/unit'
|
19
|
+
|
20
|
+
require "spec/rails/example/model_example_group"
|
21
|
+
require 'spec/rails/extensions/spec/matchers/have'
|
22
|
+
|
23
|
+
require 'spec/rails/matchers/ar_be_valid'
|
24
|
+
require 'spec/rails/matchers/change'
|
25
|
+
|
26
|
+
require 'spec/rails/mocks'
|
27
|
+
|
28
|
+
require 'spec/rails/extensions/active_support/test_case'
|
29
|
+
require 'spec/rails/extensions/active_record/base'
|
30
|
+
|
31
|
+
require 'spec/rails/interop/testcase'
|
32
|
+
|
33
|
+
Spec::Example::ExampleGroupFactory.default(ActiveSupport::TestCase)
|
34
|
+
|
35
|
+
require 'publishing_logic'
|
36
|
+
|
37
|
+
require 'logger'
|
38
|
+
ActiveRecord::Base.logger = Logger.new("test.log")
|
39
|
+
|
40
|
+
# Time zone setup Publishing Logic assumes that you've properly set up your timezones.
|
41
|
+
Time.zone_default = Time.send(:get_zone, 'UTC')
|
42
|
+
ActiveRecord::Base.time_zone_aware_attributes = true
|
43
|
+
ActiveRecord::Base.default_timezone = :utc
|
44
|
+
|
45
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
8
46
|
|
9
47
|
# require 'publishing_logic'
|
10
48
|
# require 'spec'
|
11
49
|
# require 'spec/autorun'
|
12
50
|
|
13
|
-
|
14
|
-
|
51
|
+
require 'database_cleaner'
|
52
|
+
DatabaseCleaner.strategy = :truncation
|
15
53
|
|
16
54
|
Spec::Runner.configure do |config|
|
17
|
-
|
18
55
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Spec::Runner.configure do |config|
|
2
|
+
config.before(:suite) do
|
3
|
+
DatabaseCleaner.strategy = :transaction
|
4
|
+
DatabaseCleaner.clean_with(:truncation)
|
5
|
+
end
|
6
|
+
|
7
|
+
config.before(:each) do
|
8
|
+
DatabaseCleaner.start
|
9
|
+
end
|
10
|
+
|
11
|
+
config.after(:each) do
|
12
|
+
DatabaseCleaner.clean
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module PublishingLogic
|
2
|
+
module Migrations
|
3
|
+
class AddProgrammes < ActiveRecord::Migration
|
4
|
+
def self.up
|
5
|
+
create_table :programmes do |t|
|
6
|
+
t.boolean :publishing_enabled
|
7
|
+
t.datetime :published_at
|
8
|
+
t.datetime :published_until
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :programmes, [:published_at, :publishing_enabled, :published_until], :name => 'index_programmes_on_publishing_logic_fields'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
remove_index :programmes, :name => 'index_programmes_on_publishing_logic_fields'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class AddArticles < ActiveRecord::Migration
|
21
|
+
def self.up
|
22
|
+
create_table :articles do |t|
|
23
|
+
t.boolean :publishing_enabled
|
24
|
+
t.datetime :published_at
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
add_index :programmes, [:published_at, :publishing_enabled], :name => 'index_articles_on_publishing_logic_fields'
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.down
|
32
|
+
remove_index :articles, :name => 'index_articles_on_publishing_logic_fields'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if ActiveRecord::Migrator.current_version != 2
|
39
|
+
migrator = ActiveRecord::Migrator.new(:up, '', 2)
|
40
|
+
migrator.instance_eval {
|
41
|
+
migration_1 = ActiveRecord::MigrationProxy.new
|
42
|
+
migration_1.instance_eval {
|
43
|
+
@name = 'add_programmes'
|
44
|
+
@version = 1
|
45
|
+
@migration = PublishingLogic::Migrations::AddProgrammes
|
46
|
+
}
|
47
|
+
migration_2 = ActiveRecord::MigrationProxy.new
|
48
|
+
migration_2.instance_eval {
|
49
|
+
@name = 'add_articles'
|
50
|
+
@version = 2
|
51
|
+
@migration = PublishingLogic::Migrations::AddArticles
|
52
|
+
}
|
53
|
+
@migrations = [ migration_1, migration_2 ]
|
54
|
+
}
|
55
|
+
migrator.migrate
|
56
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: publishing_logic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Unboxed Consulting
|
@@ -9,29 +15,119 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2010-
|
18
|
+
date: 2010-08-20 00:00:00 +01:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: rspec
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
23
34
|
version: 1.2.9
|
24
|
-
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
25
37
|
- !ruby/object:Gem::Dependency
|
26
38
|
name: cucumber
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
30
42
|
requirements:
|
31
43
|
- - ">="
|
32
44
|
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
33
48
|
version: "0"
|
34
|
-
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: rspec-rails
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 27
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 3
|
63
|
+
- 0
|
64
|
+
version: 1.3.0
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: factory_girl
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 31
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 2
|
79
|
+
- 0
|
80
|
+
version: 1.2.0
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
prerelease: false
|
86
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 2
|
94
|
+
- 3
|
95
|
+
- 0
|
96
|
+
version: 2.3.0
|
97
|
+
type: :development
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: activerecord
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 2
|
110
|
+
- 3
|
111
|
+
- 0
|
112
|
+
version: 2.3.0
|
113
|
+
type: :development
|
114
|
+
version_requirements: *id006
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: database_cleaner
|
117
|
+
prerelease: false
|
118
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
hash: 11
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
- 5
|
127
|
+
- 0
|
128
|
+
version: 0.5.0
|
129
|
+
type: :development
|
130
|
+
version_requirements: *id007
|
35
131
|
description: Publishing logic for ActiveRecord models
|
36
132
|
email: enquiries@unboxedconsulting.com
|
37
133
|
executables: []
|
@@ -49,19 +145,24 @@ files:
|
|
49
145
|
- README.rdoc
|
50
146
|
- Rakefile
|
51
147
|
- VERSION
|
52
|
-
- features/publishing_logic.feature
|
53
|
-
- features/step_definitions/publishing_logic_steps.rb
|
54
|
-
- features/support/env.rb
|
55
148
|
- lib/model_logic.rb
|
56
149
|
- lib/publishing_logic.rb
|
57
150
|
- rails_generators/publishing_logic_fields/USAGE
|
58
151
|
- rails_generators/publishing_logic_fields/publishing_logic_fields_generator.rb
|
59
152
|
- rails_generators/publishing_logic_fields/templates/app/views/publishing_logic_fields.html.erb
|
60
153
|
- rails_generators/publishing_logic_fields/templates/db/migrate/add_publishing_logic_fields.rb.erb
|
61
|
-
- spec/
|
62
|
-
- spec/
|
154
|
+
- spec/general_model_logic.rb
|
155
|
+
- spec/models_with_all_fields_spec.rb
|
156
|
+
- spec/models_with_no_published_until_field_spec.rb
|
63
157
|
- spec/spec.opts
|
64
158
|
- spec/spec_helper.rb
|
159
|
+
- spec/support/database_cleanliness.rb
|
160
|
+
- spec/support/database_connection.rb
|
161
|
+
- spec/support/database_migrations.rb
|
162
|
+
- spec/support/factories.rb
|
163
|
+
- spec/support/logging.rb
|
164
|
+
- spec/support/models/article.rb
|
165
|
+
- spec/support/models/programme.rb
|
65
166
|
has_rdoc: true
|
66
167
|
homepage: http://github.com/unboxed/publishing_logic
|
67
168
|
licenses: []
|
@@ -72,25 +173,39 @@ rdoc_options:
|
|
72
173
|
require_paths:
|
73
174
|
- lib
|
74
175
|
required_ruby_version: !ruby/object:Gem::Requirement
|
176
|
+
none: false
|
75
177
|
requirements:
|
76
178
|
- - ">="
|
77
179
|
- !ruby/object:Gem::Version
|
180
|
+
hash: 3
|
181
|
+
segments:
|
182
|
+
- 0
|
78
183
|
version: "0"
|
79
|
-
version:
|
80
184
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
81
186
|
requirements:
|
82
187
|
- - ">="
|
83
188
|
- !ruby/object:Gem::Version
|
189
|
+
hash: 3
|
190
|
+
segments:
|
191
|
+
- 0
|
84
192
|
version: "0"
|
85
|
-
version:
|
86
193
|
requirements: []
|
87
194
|
|
88
195
|
rubyforge_project:
|
89
|
-
rubygems_version: 1.3.
|
196
|
+
rubygems_version: 1.3.7
|
90
197
|
signing_key:
|
91
198
|
specification_version: 3
|
92
199
|
summary: Publishing logic for ActiveRecord models
|
93
200
|
test_files:
|
94
|
-
- spec/
|
95
|
-
- spec/
|
201
|
+
- spec/general_model_logic.rb
|
202
|
+
- spec/models_with_all_fields_spec.rb
|
203
|
+
- spec/models_with_no_published_until_field_spec.rb
|
96
204
|
- spec/spec_helper.rb
|
205
|
+
- spec/support/database_cleanliness.rb
|
206
|
+
- spec/support/database_connection.rb
|
207
|
+
- spec/support/database_migrations.rb
|
208
|
+
- spec/support/factories.rb
|
209
|
+
- spec/support/logging.rb
|
210
|
+
- spec/support/models/article.rb
|
211
|
+
- spec/support/models/programme.rb
|
File without changes
|
data/features/support/env.rb
DELETED
@@ -1,158 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
|
2
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "../../../../../spec/factories"))
|
3
|
-
require 'model_logic'
|
4
|
-
|
5
|
-
describe PublishingLogic::ModelLogic do
|
6
|
-
def create_objects_with_different_published_at_dates
|
7
|
-
@object2 = Factory.create(:programme, :publishing_enabled => true, :published_at => 2.days.ago)
|
8
|
-
@object1 = Factory.create(:programme, :publishing_enabled => true, :published_at => 1.days.ago)
|
9
|
-
@object3 = Factory.create(:programme, :publishing_enabled => true, :published_at => 3.days.ago)
|
10
|
-
end
|
11
|
-
|
12
|
-
describe "published?" do
|
13
|
-
describe "with publishing enabled" do
|
14
|
-
it "should be published by default" do
|
15
|
-
Factory.create(:programme,
|
16
|
-
:publishing_enabled => true,
|
17
|
-
:published_at => nil,
|
18
|
-
:published_until => nil).should be_published
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should not be published if the published_at datetime is in the future" do
|
22
|
-
Factory.create(:programme,
|
23
|
-
:publishing_enabled => true,
|
24
|
-
:published_at => 5.seconds.from_now,
|
25
|
-
:published_until => nil).should_not be_published
|
26
|
-
end
|
27
|
-
|
28
|
-
it "should not be published if the published_until datetime is in the past" do
|
29
|
-
Factory.create(:programme,
|
30
|
-
:publishing_enabled => true,
|
31
|
-
:published_at => nil,
|
32
|
-
:published_until => 5.seconds.ago).should_not be_published
|
33
|
-
end
|
34
|
-
end
|
35
|
-
describe "with publishing disabled" do
|
36
|
-
it "should not be published" do
|
37
|
-
Factory.create(:programme,
|
38
|
-
:publishing_enabled => false,
|
39
|
-
:published_at => 1.days.ago,
|
40
|
-
:published_until => 10.days.from_now).should_not be_published
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe "published named scope" do
|
46
|
-
it "should include published objects" do
|
47
|
-
programme = Factory.create(:programme, :publishing_enabled => true)
|
48
|
-
Programme.published.should == [programme]
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should not include any unpublished objects" do
|
52
|
-
Factory.create(:programme, :publishing_enabled => false)
|
53
|
-
Programme.published.should be_empty
|
54
|
-
end
|
55
|
-
|
56
|
-
# it "should not expose a published episode published an hour ago" do
|
57
|
-
# article = Factory.create(:episode, :is_hidden => false, :published_at => 1.hour.from_now, :published_until => nil)
|
58
|
-
# Episode.published.should == []
|
59
|
-
# end
|
60
|
-
|
61
|
-
it "should not include objects with a published_until in the past" do
|
62
|
-
Factory.create(:programme,
|
63
|
-
:publishing_enabled => true,
|
64
|
-
:published_until => 5.seconds.ago)
|
65
|
-
Programme.published.should be_empty
|
66
|
-
end
|
67
|
-
|
68
|
-
it "should not include objects with a published_at in the future" do
|
69
|
-
Factory.create(:programme,
|
70
|
-
:publishing_enabled => true,
|
71
|
-
:published_at => 5.seconds.from_now)
|
72
|
-
Programme.published.should be_empty
|
73
|
-
end
|
74
|
-
|
75
|
-
it "should get a new Time.now for each invocation of the named scope" do
|
76
|
-
item = Factory.create(:programme,
|
77
|
-
:publishing_enabled => true,
|
78
|
-
:published_until => 10.days.from_now)
|
79
|
-
mock_now = mock('now', :utc => 20.days.from_now, :to_f => 0)
|
80
|
-
Time.stub(:now).and_return mock_now
|
81
|
-
Programme.published.should be_empty
|
82
|
-
end
|
83
|
-
|
84
|
-
it "should use the utc of the current time" do
|
85
|
-
# Make sure utc is used, which is hard to test as a behaviour
|
86
|
-
mock_now = mock('now')
|
87
|
-
Time.stub(:now).and_return mock_now
|
88
|
-
mock_now.should_receive(:utc).twice
|
89
|
-
Programme.published
|
90
|
-
end
|
91
|
-
|
92
|
-
describe "newest" do
|
93
|
-
before do
|
94
|
-
create_objects_with_different_published_at_dates
|
95
|
-
end
|
96
|
-
|
97
|
-
it "should be the most recently published object" do
|
98
|
-
Programme.published.newest.should == @object1
|
99
|
-
end
|
100
|
-
end
|
101
|
-
describe "oldest" do
|
102
|
-
it "should be the object published the longest ago" do
|
103
|
-
Programme.published.oldest.should == @object3
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
describe "ordering by published_at" do
|
109
|
-
describe "by date oldest first" do
|
110
|
-
it "should return the items, oldest first" do
|
111
|
-
create_objects_with_different_published_at_dates
|
112
|
-
Programme.by_date_oldest_first.map(&:id).should == [@object3,
|
113
|
-
@object2,
|
114
|
-
@object1].map(&:id)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should order by created_at date if published_ats are equal" do
|
118
|
-
create_objects_with_different_published_at_dates
|
119
|
-
@object2b = Factory.create(:programme,
|
120
|
-
:publishing_enabled => true,
|
121
|
-
:published_at => @object2.published_at,
|
122
|
-
:created_at => 3.days.ago)
|
123
|
-
Programme.by_date_oldest_first.map(&:id).should == [@object3,
|
124
|
-
@object2b,
|
125
|
-
@object2,
|
126
|
-
@object1].map(&:id)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
describe "by date newest first" do
|
131
|
-
it "should return the items, oldest first" do
|
132
|
-
create_objects_with_different_published_at_dates
|
133
|
-
Programme.by_date_newest_first.map(&:id).should == [@object1,
|
134
|
-
@object2,
|
135
|
-
@object3].map(&:id)
|
136
|
-
end
|
137
|
-
|
138
|
-
it "should order by created_at date if published_ats are equal" do
|
139
|
-
create_objects_with_different_published_at_dates
|
140
|
-
@object2b = Factory.create(:programme,
|
141
|
-
:publishing_enabled => true,
|
142
|
-
:published_at => @object2.published_at,
|
143
|
-
:created_at => 3.days.from_now)
|
144
|
-
Programme.by_date_newest_first.map(&:id).should == [@object1,
|
145
|
-
@object2b,
|
146
|
-
@object2,
|
147
|
-
@object3].map(&:id)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should have a newest first ordering that is the reverse of the oldest first ordering for identical objects" do
|
152
|
-
creation_time = 2.days.ago
|
153
|
-
publish_time = 1.days.ago
|
154
|
-
5.times { Factory.create(:programme, :created_at => creation_time, :published_at => publish_time) }
|
155
|
-
Programme.by_date_newest_first.map(&:id).should == Programme.by_date_oldest_first.map(&:id).reverse
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|