acts_as_featured 0.1.2

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/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