publishable 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document CHANGED
@@ -1,5 +1,6 @@
1
1
  README.rdoc
2
2
  lib/**/*.rb
3
3
  bin/*
4
+ -
4
5
  features/**/*.feature
5
- LICENSE
6
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,43 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Add dependencies required to use your gem here.
4
+ # Example:
5
+ # gem "activesupport", ">= 2.3.5"
6
+
7
+ #==============================================================================
8
+ # Gems used in both development and testing environments.
9
+ #==============================================================================
10
+ group :development, :test do
11
+
12
+ # Use YARD documentation format
13
+ gem "yard", "~> 0.7"
14
+ gem "rdoc", "~> 3.12"
15
+ gem "bundler", "~> 1.0"
16
+
17
+ # Use Jeweler to build our gem
18
+ gem "jeweler", "~> 1.8.4"
19
+
20
+ # RSpec for our testing framework
21
+ gem "rspec", "~> 2.8.0"
22
+ end
23
+
24
+ #==============================================================================
25
+ # Gems just needed for testing.
26
+ #==============================================================================
27
+ group :test do
28
+
29
+ # Faker generates names, email addresses, and other placeholders for factories
30
+ gem 'ffaker'
31
+
32
+ # For testing ActiveRecord extensions
33
+ gem 'acts_as_fu'
34
+
35
+ # Test time-dependent functionality
36
+ gem 'timecop'
37
+
38
+ if RUBY_VERSION > '1.9'
39
+ gem "simplecov", :require => false
40
+ else
41
+ gem "rcov", ">= 0"
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.12)
5
+ activesupport (= 3.2.12)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.12)
8
+ activemodel (= 3.2.12)
9
+ activesupport (= 3.2.12)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.12)
13
+ i18n (~> 0.6)
14
+ multi_json (~> 1.0)
15
+ acts_as_fu (0.0.8)
16
+ activerecord
17
+ sqlite3
18
+ arel (3.0.2)
19
+ builder (3.0.4)
20
+ diff-lcs (1.1.3)
21
+ ffaker (1.15.0)
22
+ git (1.2.5)
23
+ i18n (0.6.1)
24
+ jeweler (1.8.4)
25
+ bundler (~> 1.0)
26
+ git (>= 1.2.5)
27
+ rake
28
+ rdoc
29
+ json (1.7.7)
30
+ multi_json (1.6.1)
31
+ rake (10.0.3)
32
+ rcov (1.0.0)
33
+ rdoc (3.12.1)
34
+ json (~> 1.4)
35
+ rspec (2.8.0)
36
+ rspec-core (~> 2.8.0)
37
+ rspec-expectations (~> 2.8.0)
38
+ rspec-mocks (~> 2.8.0)
39
+ rspec-core (2.8.0)
40
+ rspec-expectations (2.8.0)
41
+ diff-lcs (~> 1.1.2)
42
+ rspec-mocks (2.8.0)
43
+ sqlite3 (1.3.7)
44
+ timecop (0.5.9.2)
45
+ tzinfo (0.3.35)
46
+ yard (0.8.4.1)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ acts_as_fu
53
+ bundler (~> 1.0)
54
+ ffaker
55
+ jeweler (~> 1.8.4)
56
+ rcov
57
+ rdoc (~> 3.12)
58
+ rspec (~> 2.8.0)
59
+ timecop
60
+ yard (~> 0.7)
@@ -1,4 +1,5 @@
1
1
  Copyright (c) 2010 Martin Linkhorst
2
+ Copyright (c) 2013 тιηуηυмвєяѕ
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining
4
5
  a copy of this software and associated documentation files (the
@@ -0,0 +1,87 @@
1
+ = publishable
2
+
3
+ *Publishable* allows a given Boolean, Date, or DateTime column to indicate whether an ActiveRecord
4
+ model object is "published". This can be used, for example, to schedule a news post to be published
5
+ at some later date on a website, or to hide work-in-progress content until it is ready to be released
6
+ to the public.
7
+
8
+ Methods are provided to publish and unpublish the model object, and scopes are added for filtering
9
+ your records.
10
+
11
+ * When publishing via a Boolean column, the value of the column determines whether (+true+) or not (+false+)
12
+ the model object is published.
13
+ * When publishing via a Date or DateTime column, the value of the published column must be before "now"
14
+ (or "today" for Date columns) for the object to be published. Date or DateTime publishables also have
15
+ scopes for returning recent or upcoming records, i.e. records that have already been published, or records
16
+ that will be published in the future.
17
+
18
+ == Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'publishable'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install publishable
31
+
32
+ == Setup
33
+
34
+ 1. Create the publishable column via a migration:
35
+
36
+ rails generate migration add_published_to_post published:date
37
+ rails generate migration add_published_to_story published:datetime
38
+ rails generate migration add_released_to_album released:boolean
39
+
40
+ 2. Declare that your models are publishable:
41
+
42
+ class Post < ActiveRecord::Base
43
+ publishable
44
+ end
45
+
46
+ class Story < ActiveRecord::Base
47
+ publishable
48
+ end
49
+
50
+ class Album < ActiveRecord::Base
51
+ publishable :on => :released
52
+ end
53
+
54
+ The column name used by *publishable* can be specified via the +:on+ option. The default used (if +:on+
55
+ is not specified) is +:published+.
56
+
57
+ == Usage
58
+
59
+ published_posts = Post.recent(5) # Returns the five most recently-published Posts, in descending order by publish-date
60
+
61
+ album = Album.new
62
+ album.published? # false - by default, records are not published
63
+ album.publish # sets the 'released' column to true, but does not save it back to the database
64
+ album.publish! # publishes AND saves the record to the database
65
+ album.published? # true
66
+ album.unpublish! # sets the 'released' column back to false and saves the record
67
+
68
+ story = Story.new
69
+ story.publish!(2.hours.from_now) # Sets the story to go live in two hours
70
+ story.unpublish! # Sets the 'published' column to nil
71
+ upcoming_stories = Story.upcoming # Get all stories that will be published in the future, in ascending order by publish-date
72
+
73
+ == Contributing to publishable
74
+
75
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
76
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
77
+ * Fork the project.
78
+ * Start a feature/bugfix branch.
79
+ * Commit and push until you are happy with your contribution.
80
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
81
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
82
+
83
+ == Copyright
84
+
85
+ Copyright (c) 2010 Martin Linkhorst, released under the MIT license.
86
+ Copyright (c) 2013 тιηуηυмвєяѕ. See LICENSE.txt for further details.
87
+
data/Rakefile CHANGED
@@ -1,46 +1,48 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts 'Run `bundle install` to install missing gems'
10
+ exit e.status_code
11
+ end
2
12
  require 'rake'
3
13
 
4
14
  begin
5
15
  require 'jeweler'
16
+ require './lib/publishable/version.rb'
6
17
  Jeweler::Tasks.new do |gem|
7
- gem.name = "publishable"
8
- gem.summary = "Adds publishing functionality to your active record model"
9
- gem.description = "Provides methods to publish and unpublish your active record models based on a datetime. Also adds named scopes to nicely filter your records. Does not touch any controller or views."
10
- gem.email = "m.linkhorst@googlemail.com"
11
- gem.homepage = "http://github.com/linki/publishable"
12
- gem.authors = ["Martin Linkhorst"]
13
- gem.add_development_dependency "rspec", ">= 1.2.9"
14
- gem.files.include 'lib/**/*.rb'
15
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ gem.name = 'publishable'
19
+ gem.license = 'MIT'
20
+ gem.summary = 'Adds publishing functionality to your active record model'
21
+ gem.description = 'Provides methods to publish and unpublish your active record models based on a boolean flag, a date, or a datetime. Also adds named scopes to nicely filter your records. Does not touch any controller or views.'
22
+ gem.email = ['m.linkhorst@googlemail.com', 'info@tinynumbers.com']
23
+ gem.homepage = 'http://github.com/linki/publishable'
24
+ gem.authors = ['Martin Linkhorst', 'тιηуηυмвєяѕ']
25
+ gem.version = "#{Publishable::VERSION}"
26
+ # dependencies defined in Gemfile
27
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
16
28
  end
17
- Jeweler::GemcutterTasks.new
29
+ Jeweler::RubygemsDotOrgTasks.new
18
30
  rescue LoadError
19
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
31
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
20
32
  end
21
33
 
22
- require 'spec/rake/spectask'
23
- Spec::Rake::SpecTask.new(:spec) do |spec|
24
- spec.libs << 'lib' << 'spec'
25
- spec.spec_files = FileList['spec/**/*_spec.rb']
34
+ require 'rspec/core'
35
+ require 'rspec/core/rake_task'
36
+ RSpec::Core::RakeTask.new(:spec) do |spec|
37
+ spec.pattern = FileList['spec/**/*_spec.rb']
26
38
  end
27
39
 
28
- Spec::Rake::SpecTask.new(:rcov) do |spec|
29
- spec.libs << 'lib' << 'spec'
40
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
30
41
  spec.pattern = 'spec/**/*_spec.rb'
31
42
  spec.rcov = true
32
43
  end
33
44
 
34
- task :spec => :check_dependencies
35
-
36
45
  task :default => :spec
37
46
 
38
- require 'rake/rdoctask'
39
- Rake::RDocTask.new do |rdoc|
40
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
-
42
- rdoc.rdoc_dir = 'rdoc'
43
- rdoc.title = "publishable #{version}"
44
- rdoc.rdoc_files.include('README*')
45
- rdoc.rdoc_files.include('lib/**/*.rb')
46
- end
47
+ require 'yard'
48
+ YARD::Rake::YardocTask.new
@@ -1,37 +1,269 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'publishable/railtie' if defined?(Rails)
2
4
 
5
+ # Allows a given boolean, date, or datetime column to indicate whether a model object is published.
6
+ # Boolean published column just is an on/off flag.
7
+ # Date/datetime column requires value of published column to be before "now" for the object to be published.
8
+ # Specify the column name via the :on option (defaults to :published) and make sure to create the column
9
+ # in your migrations.
10
+ #
11
+ # Provides scopes for finding published and unpublished items, and (for date/datetime published columns) for returning
12
+ # recent or upcoming items.
13
+ #
14
+ # @author Martin Linkhorst <m.linkhorst@googlemail.com>
15
+ # @author David Daniell / тιηуηυмвєяѕ <info@tinynumbers.com>
3
16
  module Publishable
17
+
18
+ # Add our features to the base class.
19
+ # @see ClassMethods#publishable
20
+ # @param [Object] base
4
21
  def self.extended(base)
5
22
  base.extend ClassMethods
6
23
  end
7
-
24
+
25
+ # Define scopes and methods for querying and manipulating Publishables.
8
26
  module ClassMethods
27
+
28
+ # DSL method to link this behavior into your model. In your ActiveRecord model class, add +publishable+ to include
29
+ # the scopes and methods for publishable objects.
30
+ #
31
+ # @example
32
+ # class Post < ActiveRecord::Base
33
+ # publishable
34
+ # end
35
+ #
36
+ # @param [Hash] options The publishable options.
37
+ # @option options [String, Symbol] :on (:publishable) The name of the publishable column on the model.
9
38
  def publishable(options = {})
10
- column_name = (options[:on] || :published_at).to_s
39
+ return unless table_exists?
40
+ column_name = (options[:on] || :published).to_sym
41
+ unless self.columns_hash[column_name.to_s].present?
42
+ raise ActiveRecord::ConfigurationError, "No '#{column_name}'column available for Publishable column on model #{self.name}"
43
+ end
44
+ column_type = self.columns_hash[column_name.to_s].type
11
45
 
12
46
  if respond_to?(:scope)
13
- scope :published, lambda { |time = Time.now|
14
- where("#{column_name} IS NOT NULL AND #{column_name} <= ?", time.utc)
15
- }
16
47
 
17
- scope :unpublished, lambda { |time = Time.now|
18
- where("#{column_name} IS NULL OR #{column_name} > ?", time.utc)
19
- }
20
- end
21
-
22
- class_eval <<-EVIL, __FILE__, __LINE__ + 1
23
- def published?(time = Time.now)
24
- #{column_name} ? #{column_name} <= time : false
48
+ # define published/unpublished scope
49
+ case column_type
50
+ when :date
51
+ scope :published, lambda { |*args|
52
+ on_date = args[0] || Date.current
53
+ where(arel_table[column_name].not_eq(nil)).where(arel_table[column_name].lteq(on_date))
54
+ }
55
+
56
+ scope :unpublished, lambda { |*args|
57
+ on_date = args[0] || Date.current
58
+ where(arel_table[column_name].not_eq(nil)).where(arel_table[column_name].gt(on_date))
59
+ }
60
+
61
+ when :datetime
62
+ scope :published, lambda { |*args|
63
+ at_time = args[0] || Time.now
64
+ where(arel_table[column_name].not_eq(nil)).where(arel_table[column_name].lteq(at_time.utc))
65
+ }
66
+
67
+ scope :unpublished, lambda { |*args|
68
+ at_time = args[0] || Time.now
69
+ where(arel_table[column_name].not_eq(nil)).where(arel_table[column_name].gt(at_time.utc))
70
+ }
71
+
72
+ when :boolean
73
+ scope :published, lambda {
74
+ where(column_name => true)
75
+ }
76
+
77
+ scope :unpublished, lambda {
78
+ where(column_name => false)
79
+ }
80
+
81
+ else
82
+ raise ActiveRecord::ConfigurationError, "Invalid column_type #{column_type} for Publishable column on model #{self.name}"
25
83
  end
26
84
 
27
- def publish(time = Time.now)
28
- self.#{column_name} = time unless published?(time)
85
+ # define recent/upcoming scopes
86
+ if [:date, :datetime].include? column_type
87
+ scope :recent, lambda { |*args|
88
+ how_many = args[0] || nil
89
+ col_name = arel_table[column_name].name
90
+ published.limit(how_many).order("#{col_name} DESC")
91
+ }
92
+ scope :upcoming, lambda { |*args|
93
+ how_many = args[0] || nil
94
+ col_name = arel_table[column_name].name
95
+ unpublished.limit(how_many).order("#{col_name} ASC")
96
+ }
29
97
  end
30
98
 
31
- def publish!(time = Time.now)
32
- publish(time) && (!respond_to?(:save) || save)
33
- end
34
- EVIL
99
+ end
100
+
101
+ case column_type
102
+ when :datetime
103
+ class_eval <<-EVIL, __FILE__, __LINE__ + 1
104
+ def published?(_when = Time.now)
105
+ #{column_name} ? #{column_name} <= _when : false
106
+ end
107
+
108
+ def unpublished?(_when = Time.now)
109
+ !published?(_when)
110
+ end
111
+
112
+ def publish(_when = Time.now)
113
+ self.#{column_name} = _when unless published?(_when)
114
+ end
115
+
116
+ def publish!(_when = Time.now)
117
+ publish(_when) && (!respond_to?(:save) || save)
118
+ end
119
+
120
+ def unpublish()
121
+ self.#{column_name} = null
122
+ end
123
+
124
+ def unpublish!()
125
+ unpublish() && (!respond_to?(:save) || save)
126
+ end
127
+ EVIL
128
+
129
+ when :date
130
+ class_eval <<-EVIL, __FILE__, __LINE__ + 1
131
+ def published?(_when = Date.current)
132
+ #{column_name} ? #{column_name} <= _when : false
133
+ end
134
+
135
+ def unpublished?(_when = Date.current)
136
+ !published?(_when)
137
+ end
138
+
139
+ def publish(_when = Date.current)
140
+ self.#{column_name} = _when unless published?(_when)
141
+ end
142
+
143
+ def publish!(_when = Date.current)
144
+ publish(_when) && (!respond_to?(:save) || save)
145
+ end
146
+
147
+ def unpublish()
148
+ self.#{column_name} = null
149
+ end
150
+
151
+ def unpublish!()
152
+ unpublish() && (!respond_to?(:save) || save)
153
+ end
154
+ EVIL
155
+
156
+ when :boolean
157
+ class_eval <<-EVIL, __FILE__, __LINE__ + 1
158
+ def published?()
159
+ #{column_name}
160
+ end
161
+
162
+ def unpublished?()
163
+ !published?()
164
+ end
165
+
166
+ def publish()
167
+ self.#{column_name} = true
168
+ end
169
+
170
+ def publish!()
171
+ publish()
172
+ save if respond_to?(:save)
173
+ end
174
+
175
+ def unpublish()
176
+ self.#{column_name} = false
177
+ end
178
+
179
+ def unpublish!()
180
+ unpublish()
181
+ save if respond_to?(:save)
182
+ end
183
+ EVIL
184
+
185
+ else
186
+ raise ActiveRecord::ConfigurationError, "Invalid column_type #{column_type} for Publishable column on model #{self.name}"
187
+ end
188
+
35
189
  end
190
+
191
+ # @!group Query scopes added to publishable models
192
+
193
+ # @!method published
194
+ # Query scope added to publishables that can be used to find published records. For Date/DateTime publishables,
195
+ # you can pass a specific date on which the results should be published.
196
+ # @example Find only records that are currently published
197
+ # published_posts = Post.published
198
+ # @example Find only records that will be published in two days
199
+ # future_posts = Post.published(Date.current + 2.days)
200
+ # @param [Date, Time, nil] when Specify a date/time for Date/DateTime publishables - defaults to the current date/time
201
+ # @!scope class
202
+
203
+ # @!method unpublished
204
+ # Query scope added to publishables that can be used find records which are not published. For Date/DateTime
205
+ # publishables, you can pass a specific date on which the results should not have been published.
206
+ # @example Find only records that are not currently published
207
+ # unpublished_posts = Post.unpublished
208
+ # @param [Date, Time, nil] when Specify a date/time for Date/DateTime publishables - defaults to the current date/time
209
+ # @!scope class
210
+
211
+ # @!method recent
212
+ # Query scope added to publishables that can be used to lookup records which are currently published. The results
213
+ # are returned in descending order based on the published date/time.
214
+ # @example Get the 10 most recently-published records
215
+ # recent_posts = Post.recent(10)
216
+ # @param [Integer, nil] how_many Specify how many records to return
217
+ # @!scope class
218
+
219
+ # @!method upcoming
220
+ # Query scope added to publishables that can be used to lookup records which are not currently published. The
221
+ # results are returned in ascending order based on the published date/time.
222
+ # @example Get all posts that will be published in the future
223
+ # upcoming_posts = Post.upcoming
224
+ # @param [Integer, nil] how_many Specify how many records to return
225
+ # @!scope class
226
+
227
+ # @!endgroup
228
+
229
+ # @!group Instance methods added to publishable models
230
+
231
+ # @!method published?
232
+ # Is this object published?
233
+ # @param [Date, Time, nil] when For Date/DateTime publishables, a date/time can be passed to determine if the
234
+ # object was / will be published on the given date.
235
+ # @return [Boolean] true if published, false if not published.
236
+ # @!scope instance
237
+
238
+ # @!method unpublished?
239
+ # Is this object not published?
240
+ # @param [Date, Time, nil] when For Date/DateTime publishables, a date/time can be passed to determine if the
241
+ # object was not / will not be published on the given date.
242
+ # @return [Boolean] false if published, true if not published.
243
+ # @!scope instance
244
+
245
+ # @!method publish
246
+ # Publish this object. For a Boolean publish field, the field is set to true; for a Date/DateTime field, the
247
+ # field is set to the given Date/Time or to the current date/time.
248
+ # @param [Date, Time, nil] when For Date/DateTime publishables, a date/time can be passed to specify when the
249
+ # record will be published. Defaults to +Date.current+ or +Time.now+.
250
+ # @!scope instance
251
+
252
+ # @!method publish!
253
+ # Publish this object, then immediately save it to the database.
254
+ # @param [Date, Time, nil] when
255
+ # @!scope instance
256
+
257
+ # @!method unpublish
258
+ # Un-publish this object, i.e. set it to not be published. For a Boolean publish field, the field is set to
259
+ # false; for a Date/DateTime field, the field is set to null.
260
+ # @!scope instance
261
+
262
+ # @!method unpublish!
263
+ # Un-publish this object, then immediately save it to the database.
264
+ # @!scope instance
265
+
266
+ # @!endgroup
267
+
36
268
  end
37
- end
269
+ end