acts_as_featured 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gem 'rspec', '~> 2.6.0'
4
+ gem 'sqlite3'
5
+ gem 'sqlite3-ruby', :require => 'sqlite3'
6
+ gem 'activerecord', :require => 'active_record'
7
+
8
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Brandan Lennox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,41 @@
1
+ Acts as Featured
2
+ ================
3
+
4
+ Designates an attribute on this model to indicate "featuredness," where only one record within a given scope may be featured at a time.
5
+
6
+ Pass in the name of the attribute and an options hash:
7
+
8
+ * `:scope` - If given, designates the scope in which this model is featured. This would typically be a `belongs_to` association.
9
+ * `:create_scope` - If `true`, creates a named scope using the name of the attribute given here. If it's a symbol, creates a named scope using that symbol.
10
+
11
+ Examples:
12
+
13
+ class Project < ActiveRecord::Base
14
+ # no two Projects will ever have their @featured attributes set simultaneously
15
+ acts_as_featured :featured
16
+ end
17
+
18
+ class Photo < ActiveRecord::Base
19
+ # each account gets a favorite photo
20
+ belongs_to :account
21
+ acts_as_featured :favorite, :scope => :account
22
+ end
23
+
24
+ class Article < ActiveRecord::Base
25
+ # creates a named scope called Article.featured to return the featured article
26
+ acts_as_featured :main, :create_scope => :featured
27
+ end
28
+
29
+ Running Tests
30
+ -------------
31
+
32
+ It's RSpec:
33
+
34
+ rspec spec/acts_as_featured_spec.rb
35
+
36
+ Make sure you run `bundle install` to get the development dependencies first.
37
+
38
+ License
39
+ -------
40
+
41
+ See MIT-LICENSE.
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acts_as_featured/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acts_as_featured"
7
+ s.version = ActsAsFeatured::VERSION
8
+ s.authors = ["Brandan Lennox"]
9
+ s.email = ["brandan@bclennox.com"]
10
+ s.homepage = "https://github.com/bclennox/acts_as_featured"
11
+ s.date = "2012-01-28"
12
+ s.summary = %q{Designate a Rails model attribute as unique within a scope}
13
+ s.description = %q{Designates an attribute on this model to indicate "featuredness," where only one record within a given scope may be featured at a time.}
14
+
15
+ s.rubyforge_project = "acts_as_featured"
16
+
17
+ s.files = %w{
18
+ acts_as_featured.gemspec
19
+ Gemfile
20
+ MIT-LICENSE
21
+ README.markdown
22
+
23
+ lib/acts_as_featured.rb
24
+ lib/acts_as_featured/version.rb
25
+ }
26
+
27
+ s.test_files = %w{
28
+ spec/acts_as_featured_spec.rb
29
+ spec/models.rb
30
+ spec/spec_helper.rb
31
+ }
32
+
33
+ s.require_paths = ["lib"]
34
+
35
+ s.add_dependency "activerecord", ">= 3.0.0"
36
+ s.add_development_dependency "rspec"
37
+ s.add_development_dependency "sqlite3"
38
+ s.add_development_dependency "sqlite3-ruby"
39
+ end
@@ -0,0 +1,93 @@
1
+ require 'acts_as_featured/version'
2
+
3
+ module ActsAsFeatured
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ # Designates an attribute on this model to indicate "featuredness," where
9
+ # only one record within a given scope may be featured at a time.
10
+ #
11
+ # Pass in the name of the attribute and an options hash:
12
+ #
13
+ # * <tt>:scope</tt> - If given, designates the scope in which this model is featured. This would typically be a <tt>belongs_to</tt> association.
14
+ # * <tt>:create_scope</tt> - If <tt>true</tt>, creates a named scope using the name of the attribute given here. If it's a symbol, creates a named scope using that symbol.
15
+ #
16
+ # class Project < ActiveRecord::Base
17
+ # # no two Projects will ever have their @featured attributes set simultaneously
18
+ # acts_as_featured :featured
19
+ # end
20
+ #
21
+ # class Photo < ActiveRecord::Base
22
+ # # each account gets a favorite photo
23
+ # belongs_to :account
24
+ # acts_as_featured :favorite, :scope => :account
25
+ # end
26
+ #
27
+ # class Article < ActiveRecord::Base
28
+ # # creates a named scope called Article.featured to return the featured article
29
+ # acts_as_featured :main, :create_scope => :featured
30
+ # end
31
+ def acts_as_featured(attribute, options = {})
32
+ cattr_accessor :featured_attribute
33
+ cattr_accessor :featured_attribute_scope
34
+
35
+ self.featured_attribute = attribute
36
+ self.featured_attribute_scope = options[:scope] || false
37
+
38
+ if scope_name = options[:create_scope]
39
+ scope_name = attribute if scope_name === true
40
+ scope scope_name, where(attribute => true).limit(1)
41
+ end
42
+
43
+ before_save :remove_featured_from_other_records
44
+ after_save :add_featured_to_first_record
45
+ before_destroy :add_featured_to_first_record_if_featured
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # <tt>before_save</tt> callback: If we're designating this record to be
52
+ # featured, we should clear that status on other records before saving this one.
53
+ def remove_featured_from_other_records
54
+ if scope && send(featured_attribute)
55
+ # I hope I find a better way to do this
56
+ scope.update_all(["#{featured_attribute} = ?", false], "id != #{id || 0}")
57
+ end
58
+ end
59
+
60
+ # <tt>after_save</tt> callback. If this save will result in no featured, just
61
+ # make the first record featured.
62
+ def add_featured_to_first_record
63
+ if scope && scope.count(:conditions => { featured_attribute => true }) == 0
64
+ scope.first.update_attribute(featured_attribute, true)
65
+ end
66
+ end
67
+
68
+ # <tt>before_destroy</tt> callback. If we destroy the featured, make the first
69
+ # unfeaturedmain the featured. If this was the last record, don't do anything.
70
+ def add_featured_to_first_record_if_featured
71
+ if scope && send(featured_attribute) && scope.count > 1
72
+ new_main = scope.find(:first, :conditions => { featured_attribute => false })
73
+ new_main.update_attribute(featured_attribute, true) unless new_main.nil?
74
+ end
75
+ end
76
+
77
+ def featured_attribute
78
+ @featured_attribute ||= self.class.featured_attribute
79
+ end
80
+
81
+ # Either the model class or the scope given to acts_as_featured (probably a
82
+ # belongs_to association).
83
+ def scope
84
+ association = self.class.featured_attribute_scope
85
+ @scope ||= if association
86
+ send(association).try(self.class.to_s.underscore.pluralize)
87
+ else
88
+ self.class
89
+ end
90
+ end
91
+ end
92
+
93
+ ActiveRecord::Base.send :include, ActsAsFeatured
@@ -0,0 +1,3 @@
1
+ module ActsAsFeatured
2
+ VERSION = '0.1.2'
3
+ end
@@ -0,0 +1,107 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe ActsAsFeatured do
4
+ def featured(model)
5
+ model.find_all_by_featured(true)
6
+ end
7
+
8
+ describe "unscoped" do
9
+ before(:each) do
10
+ 3.times { Thingy.create(:featured => false) }
11
+ end
12
+
13
+ after(:each) do
14
+ Thingy.delete_all
15
+ end
16
+
17
+ it 'should leave featured status alone when saving the currently featured thingy' do
18
+ originally_featured = Thingy.create(:featured => true)
19
+ featured(Thingy).first.should == originally_featured
20
+ originally_featured.save
21
+ featured(Thingy).first.should == originally_featured
22
+ end
23
+
24
+ it 'should remove featured status from the currently featured thingy when setting it on another thingy' do
25
+ originally_featured = Thingy.create(:featured => true)
26
+ will_be_featured = Thingy.create(:featured => false)
27
+
28
+ will_be_featured.featured = true
29
+ will_be_featured.save!
30
+
31
+ originally_featured.reload
32
+ will_be_featured.reload
33
+
34
+ featured(Thingy).should have(1).item
35
+ originally_featured.featured?.should be_false
36
+ will_be_featured.featured?.should be_true
37
+ end
38
+
39
+ it 'should set another thingy to be featured when removing featured status from the featured thingy' do
40
+ 3.times { Thingy.create(:featured => false) }
41
+ originally_featured = Thingy.create(:featured => true)
42
+
43
+ originally_featured.featured = false
44
+ originally_featured.save!
45
+ originally_featured.reload
46
+
47
+ featured(Thingy).should have(1).item
48
+ originally_featured.featured?.should be_false
49
+ end
50
+
51
+ it 'should set another thingy to be featured when destroying the featured thingy' do
52
+ 3.times { Thingy.create(:featured => false) }
53
+ originally_featured = Thingy.create(:featured => true)
54
+
55
+ originally_featured.destroy
56
+
57
+ featured(Thingy).should have(1).item
58
+ end
59
+ end
60
+
61
+ describe "with a scope" do
62
+ after(:each) do
63
+ ThingyAggregator.delete_all
64
+ ScopedThingy.delete_all
65
+ end
66
+
67
+ it 'should constrain featured status to scope' do
68
+ aggregator1 = ThingyAggregator.create
69
+ aggregator2 = ThingyAggregator.create
70
+
71
+ 3.times { aggregator1.scoped_thingies.create(:featured => false) }
72
+ 3.times { aggregator2.scoped_thingies.create(:featured => false) }
73
+ f1 = aggregator1.scoped_thingies.create(:featured => true)
74
+ f2 = aggregator2.scoped_thingies.create(:featured => true)
75
+
76
+ featured(ScopedThingy).should have(2).items
77
+
78
+ f1.update_attribute(:featured, false)
79
+
80
+ featured(ScopedThingy).should have(2).items
81
+ aggregator2.scoped_thingies.find_by_featured(true).should == f2
82
+ end
83
+
84
+ it 'should not explode when the scope is nil' do
85
+ expect {
86
+ ScopedThingy.create!(:featured => true)
87
+ featured(ScopedThingy).should have(1).item
88
+ }.not_to raise_error
89
+ end
90
+ end
91
+
92
+ describe "named scope" do
93
+ it 'should add a default named scope' do
94
+ DefaultNamedScopeThingy.should respond_to(:featured)
95
+ end
96
+
97
+ it 'should add a custom named scope' do
98
+ CustomNamedScopeThingy.should respond_to(:special)
99
+ end
100
+
101
+ it 'should return the featured thingy from the named scope' do
102
+ 3.times { DefaultNamedScopeThingy.create!(:featured => false) }
103
+ t = DefaultNamedScopeThingy.create!(:featured => true)
104
+ DefaultNamedScopeThingy.featured.first.should == t
105
+ end
106
+ end
107
+ end
data/spec/models.rb ADDED
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'acts_as_featured')
2
+
3
+ # a basic model
4
+ class Thingy < ActiveRecord::Base
5
+ acts_as_featured :featured, :create_scope => true
6
+ end
7
+
8
+ # a model that belongs_to another model
9
+ class ScopedThingy < ActiveRecord::Base
10
+ belongs_to :thingy_aggregator
11
+ acts_as_featured :featured, :scope => :thingy_aggregator
12
+ end
13
+ class ThingyAggregator < ActiveRecord::Base
14
+ has_many :scoped_thingies
15
+ end
16
+
17
+ # a model with a default named scope
18
+ class DefaultNamedScopeThingy < ActiveRecord::Base
19
+ acts_as_featured :featured, :create_scope => true
20
+ end
21
+
22
+ # a model with a custom named scope
23
+ class CustomNamedScopeThingy < ActiveRecord::Base
24
+ acts_as_featured :featured, :create_scope => :special
25
+ end
26
+
27
+ ActiveRecord::Migration.verbose = false
28
+ ActiveRecord::Schema.define(:version => 1) do
29
+ [:thingies, :named_scope_thingies, :default_named_scope_thingies].each do |name|
30
+ create_table name, :force => true do |t|
31
+ t.boolean :featured
32
+ end
33
+ end
34
+
35
+ create_table :thingy_aggregators, :force => true do |t|
36
+ t.timestamps
37
+ end
38
+
39
+ create_table :scoped_thingies, :force => true do |t|
40
+ t.belongs_to :thingy_aggregator
41
+ t.boolean :featured
42
+ end
43
+ end
44
+ ActiveRecord::Migration.verbose = true
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => File.join(File.dirname(__FILE__), 'test.sqlite3'))
4
+
5
+ require File.join(File.dirname(__FILE__), 'models')
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_featured
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
11
+ platform: ruby
12
+ authors:
13
+ - Brandan Lennox
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 3
31
+ - 0
32
+ - 0
33
+ version: 3.0.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: sqlite3
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: sqlite3-ruby
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: Designates an attribute on this model to indicate "featuredness," where only one record within a given scope may be featured at a time.
79
+ email:
80
+ - brandan@bclennox.com
81
+ executables: []
82
+
83
+ extensions: []
84
+
85
+ extra_rdoc_files: []
86
+
87
+ files:
88
+ - acts_as_featured.gemspec
89
+ - Gemfile
90
+ - MIT-LICENSE
91
+ - README.markdown
92
+ - lib/acts_as_featured.rb
93
+ - lib/acts_as_featured/version.rb
94
+ - spec/acts_as_featured_spec.rb
95
+ - spec/models.rb
96
+ - spec/spec_helper.rb
97
+ homepage: https://github.com/bclennox/acts_as_featured
98
+ licenses: []
99
+
100
+ post_install_message:
101
+ rdoc_options: []
102
+
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ requirements: []
124
+
125
+ rubyforge_project: acts_as_featured
126
+ rubygems_version: 1.8.15
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Designate a Rails model attribute as unique within a scope
130
+ test_files:
131
+ - spec/acts_as_featured_spec.rb
132
+ - spec/models.rb
133
+ - spec/spec_helper.rb