archive_tree 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Diogo Almeida
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
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ ## Introduction
2
+
3
+ ArchiveTree is a plugin for your Ruby on Rails application that makes it easy for you to generate structured trees of your records.
4
+
5
+ Since it makes use of your model's _created_at_ column, ArchiveTree is ultimately compatible with most ActiveRecord Models out there.
6
+
7
+
8
+ ## Installation
9
+
10
+ You can install ArchiveTree can be installed as a Ruby gem. This is specially made easy with Bundler. Just add this to your Gemfile
11
+
12
+ <pre>
13
+ gem 'archive_tree'
14
+ </pre>
15
+
16
+ It's also possible to install ArchiveTree as a Rails plugin
17
+
18
+ <pre>
19
+ rails plugin install git://github.com/GnomesLab/archive_tree.git
20
+ </pre>
21
+
22
+
23
+ ## Examples
24
+
25
+ Imagine that you have a blog application with Posts. Great!
26
+
27
+ Now let's say that you wish to allow your users to sweep through your posts in a chronologically accurate tree view. Enters ArchiveTree!
28
+
29
+ ### API
30
+
31
+ #### Default usage:
32
+
33
+ <pre>
34
+ Post.archive_tree #=> { 2010 => { 1 => [Post], 2 => [Post] },
35
+ 2011 => { 1 => [Post], 4 => [Post], 8 => [Post] } }
36
+ </pre>
37
+
38
+ #### Sweep all months of the current year:
39
+
40
+ <pre>
41
+ Post.archive_tree(:years => [Time.now.year]) #=> { 2010 => { 1 => [Post], 2 => [Post] } }
42
+ </pre>
43
+
44
+ #### Skip all months other than January (1):
45
+
46
+ <pre>
47
+ Post.archive_tree(:months => [1]) #=> { 2010 => { 1 => [Post] },
48
+ { 2011 => { 1 => [Post] } } }
49
+ </pre>
50
+
51
+ #### Only sweep January 2010:
52
+
53
+ <pre>
54
+ Post.archive_tree(:years_and_months => { 2010 => [1] }) #=> { 2010 => { 1 => [Post] } }
55
+ </pre>
56
+
57
+ ### Views
58
+
59
+ TODO: add view examples
60
+
61
+ ## Documentation
62
+
63
+ This gem's documentation documentation is available at http://yardoc.org/docs/GnomesLab-archive_tree
64
+
65
+
66
+ ## License
67
+
68
+ Copyright (c) 2010 Diogo Almeida, released under the MIT license. For more information regarding MIT license, please check our [MIT license file](http://github.com/GnomesLab/archive_tree/blob/master/MIT-LICENSE)
69
+
70
+
71
+ ## Feedback, issues and contributions
72
+
73
+ If you have an issue with ArchiveTree please create a ticket in our [issue tracker](http://gnomeslab.lighthouseapp.com/projects/57307-archive_tree/overview).
74
+
75
+ Feel free to fork this project at any time and submit your changes (along with their respective tests).
76
+
77
+ Should you just wish to provide feedback or say hi, you can always contact us directly through diogo (dot) almeida (at) gnomeslab (dot) com
@@ -0,0 +1,68 @@
1
+ module ArchiveTree
2
+ module ActionViewExtensions
3
+
4
+ # Defines the helper methods that will be included in the ActionView::Base in order to return the html representation of the archive tree
5
+ module DrawArchiveTree
6
+
7
+ # In the presence of records for a given model it draws the archive tree. Otherwise, returns an empty string
8
+ #
9
+ # This method relies on the following private methods:
10
+ # * +draw_years+
11
+ # * +draw_months+
12
+ #
13
+ # Default behavior
14
+ # * It will attempt to create a tree of your Post model
15
+ # * Will use the post_published_at_path route
16
+ # * Will display a toggle link
17
+ # * Will use "[ + ]" as the link text
18
+ #
19
+ # Example using the default settings:
20
+ # <%= draw_archive_tree %>
21
+ #
22
+ # Overriding the defaults example:
23
+ # <%= draw_archive_tree :archive, :archive_published_at_path %>
24
+
25
+ def draw_archive_tree(model_sym = :post, route = :posts_path, toggle = true, toggle_text = '[ + ]')
26
+ model = model_sym.to_s.capitalize.constantize
27
+
28
+ raw model.count > 0 ? draw_years(model_sym, route, toggle, toggle_text) : ""
29
+ end # draw_archive_tree
30
+
31
+ private
32
+ def draw_years(model_sym, route, toggle, toggle_text) # :nodoc:
33
+ model = model_sym.to_s.capitalize.constantize
34
+ route = :posts_path unless self.respond_to? route
35
+
36
+ content_tag :ul do
37
+ ul_body = ""
38
+
39
+ model.archived_years.each_key do |year, count|
40
+ ul_body << content_tag(:li, :class => year == Time.now.year ? 'active' : 'inactive') do
41
+ (toggle ? link_to(toggle_text, "#", :class => "toggle") : '') + " " +
42
+ link_to(year, self.send(route, year)) +
43
+ draw_months(model_sym, route, year)
44
+ end
45
+ end
46
+
47
+ ul_body
48
+ end
49
+ end
50
+
51
+ def draw_months(model_sym, route, year) # :nodoc:
52
+ model = model_sym.to_s.capitalize.constantize
53
+
54
+ content_tag :ul do
55
+ ul_body = ""
56
+
57
+ model.archived_months(:year => year).each_pair do |month, count|
58
+ ul_body << content_tag(:li, link_to("#{Date::MONTHNAMES[month]} (#{count})",
59
+ self.send(route, year, month < 10 ? "0#{month}" : month)))
60
+ end
61
+
62
+ ul_body
63
+ end
64
+ end
65
+ end # DrawArchiveTree
66
+
67
+ end # ActionViewExtensions
68
+ end # ArchiveTree
@@ -0,0 +1,8 @@
1
+ module ArchiveTree
2
+
3
+ module ActionViewExtensions
4
+ require 'archive_tree/action_view_extensions/draw_archive_tree'
5
+ ::ActionView::Base.send :include, DrawArchiveTree
6
+ end # ActionViewExtensions
7
+
8
+ end # ArchiveTree
@@ -0,0 +1,153 @@
1
+ module ArchiveTree
2
+
3
+ module Core #:nodoc:
4
+
5
+ # Named scope that retrieves the records for a given year and month.
6
+ #
7
+ # Note: This scope can be chained, e.g. Post.archive_node.where('id > 100')
8
+ #
9
+ # Default behaviors
10
+ # * :year #=> defaults to the current year (e.g. 2010)
11
+ # * :month #=> all
12
+ #
13
+ # Example using default values:
14
+ # Post.archive_node
15
+ #
16
+ # Example overridding values:
17
+ # Post.archive_node(:year => 2010, :month => 1)
18
+ def archive_node(options={})
19
+ options.reverse_merge! ({ :year => Time.now.year })
20
+
21
+ where("#{date_field} IS NOT NULL").
22
+ where("YEAR(#{date_field}) = ?", options[:year]).
23
+ where("MONTH(#{date_field}) = :month OR :month IS NULL", :month => options[:month])
24
+ end
25
+
26
+ # Constructs a single-level hash of years using the defined +date_field+ column.
27
+ #
28
+ # The returned hash is a key-value-pair of integers. The key represents the year (in integer) and the value
29
+ # represents the number of records for that year (also in integer).
30
+ #
31
+ # This method executes a SQL query of type COUNT, grouping the results by year.
32
+ #
33
+ # Note: the query makes use of the YEAR sql command, which might not be supported by all RDBMs.
34
+ #
35
+ # Exampe:
36
+ # Post.years_hash #=> { 2009 => 8, 2010 => 30 }
37
+ def archived_years
38
+ years = {}
39
+ where("#{date_field} IS NOT NULL").
40
+ group("YEAR(#{date_field})").size.each { |year, count| years[year.to_i] = count }
41
+
42
+ years
43
+ end # archived_years
44
+
45
+ # For a given year, constructs a single-level hash of months using the defined +date_field+ column.
46
+ #
47
+ # The returned hash is a key-value-pair representing the number of records for a given month, within a given years.
48
+ # This hash can have string or integer keys, depending on the value of :month_names option,
49
+ # which can be passed in the options hash.
50
+ #
51
+ # Default behaviors
52
+ # * By default the months are returned in their integer representation (eg.: 1 represents January)
53
+ # * The current year is assumed to be the default scope of the query
54
+ #
55
+ # Options
56
+ # * :year #=> Integer representing the year to sweep. Defaults to the current year.
57
+ # * :month_names #=> Null, or absent will result in months represented as integer (default). Also accepts :long
58
+ # and :short, depending on the desired lenght length for the month name.
59
+ #
60
+ # *Considerations*
61
+ # Given the way the queries are currently constructed and executed this method suffers from poor performance.
62
+ #
63
+ # TODO: Optimize the queries.
64
+ def archived_months(options = {})
65
+ months = {}
66
+ month_format = options.delete(:month_names) || :int
67
+
68
+ where("YEAR(#{date_field}) = #{options[:year] || Time.now.year}").
69
+ group("MONTH(#{date_field})").size.each do |month, c|
70
+ key = case month_format
71
+ when :long
72
+ Date::MONTHNAMES[month.to_i]
73
+ when :short
74
+ Date::ABBR_MONTHNAMES[month.to_i]
75
+ else
76
+ month.to_i
77
+ end
78
+
79
+ months[key] = c
80
+ end
81
+
82
+ months
83
+ end #archived_months
84
+
85
+ # Constructs an archive tree in the form of a nested Hash.
86
+ #
87
+ # Hash levels
88
+ # 1. Years (integer)
89
+ # 2. Months (integer)
90
+ # 3. Your records (ActiveRecord::Relation)
91
+ #
92
+ # Default behaviors to take note:
93
+ # * All records are sweeped by default based on their +date_field+ column
94
+ # * Years without records are not returned
95
+ # * Months without records are not returned
96
+ # * The keys are integers, thus they will likely require conversion
97
+ #
98
+ # Options
99
+ # * :years => Array of years to sweep
100
+ # * :months => Array of months to sweep of each year
101
+ # * :years_and_months => Hash of years, each containing an Array of months to sweep
102
+ # * :month_names => Accepts one of two symbols :long and :short. Please note that this overrides the default value
103
+ #
104
+ # Examples context
105
+ # Please note that for the sake of simplicity these examples assume that any given year has only one +Post+
106
+ # record for any given month and will be represented in the following format:
107
+ # { 2010 => {1 => [post]} }
108
+ #
109
+ # Default usage:
110
+ # Post.archive_tree #=> { 2010 => { 1 => [Post], 2 => [Post] },
111
+ # 2011 => { 1 => [Post], 4 => [Post], 8 => [Post] } }
112
+ #
113
+ # Sweep all months of the current year:
114
+ # Post.archive_tree(:years => [Time.now.year]) #=> { 2010 => { 1 => [Post], 2 => [Post] } }
115
+ #
116
+ # Skip all months other than January (1):
117
+ # Post.archive_tree(:months => [1]) #=> { 2010 => { 1 => [Post] },
118
+ # { 2011 => { 1 => [Post] } } }
119
+ #
120
+ # Only sweep January 2010:
121
+ # Post.archive_tree(:years_and_months => { 2010 => [1] }) #=> { 2010 => { 1 => [Post] } }
122
+ #
123
+ # *Considerations*
124
+ # This method has poor performance due to the 'linear' way in which the queries are being constructed and executed.
125
+ #
126
+ # TODO: Optimize the queries.
127
+ def archive_tree(options = {})
128
+ tree = {}
129
+ years = options[:years_and_months] ? options[:years_and_months].keys : nil || options[:years] ||
130
+ archived_years.keys || []
131
+
132
+ years.each do |year|
133
+ tree[year] = {}
134
+ months = archived_months(:year => year).keys
135
+
136
+ if options[:years_and_months]
137
+ months.reject! { |m| !options[:years_and_months][year].include?(m) }
138
+ elsif options[:months]
139
+ months.reject! { |m| !options[:months].include?(m) }
140
+ end
141
+
142
+ months.each do |month|
143
+ tree[year][month] = {}
144
+ tree[year][month] = archive_node year, month
145
+ end
146
+ end
147
+
148
+ tree
149
+ end # archive_tree
150
+
151
+ end # Core
152
+
153
+ end # ArchiveTree
@@ -0,0 +1,37 @@
1
+ # +ArchiveTree+ is responsible for the creation of hashes that cronologically represent your model
2
+ # based on a provided field
3
+ #
4
+ # If you wish to take advantage of its functionalities, please use the acts_as_archive method in your ActiveRecord Model.
5
+ #
6
+ # Examples
7
+ # class Post < ActiveRecord::Base
8
+ # acts_as_archive # uses +created_at+ by default
9
+ # end
10
+ #
11
+ # class Post < ActiveRecord::Base
12
+ # acts_as_archive :published_at # uses +published_at+ instead of +created_at+ (default)
13
+ # end
14
+ #
15
+ # Post.archive_tree(:years_and_months => { 2010 => [1] }) #=> { 2010 => { 1 => [Post] } }
16
+ #
17
+ # TODO: This module should undergo a query optimization. Furthermore, an ORM abstraction.
18
+ module ArchiveTree
19
+
20
+ require 'archive_tree/action_view_extensions'
21
+ autoload :Core, 'archive_tree/core'
22
+
23
+ def acts_as_archive(date_field = :created_at)
24
+ raise ::ArgumentError, "undefined parameter #{date_field.to_s}" unless column = columns_hash[date_field.to_s]
25
+ raise ::ArgumentError, "invalid parameter #{date_field.to_s}" unless column.type == :datetime
26
+
27
+ self.date_field = date_field # Stores the date column
28
+
29
+ extend Core
30
+ end
31
+
32
+ private
33
+ attr_accessor :date_field
34
+
35
+ end # ArchiveTree
36
+
37
+ ActiveRecord::Base.send :extend, ArchiveTree if defined?(ActiveRecord::Base)
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archive_tree
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ - rc
10
+ version: 1.0.0.rc
11
+ platform: ruby
12
+ authors:
13
+ - Diogo Almeida
14
+ - Miguel Teixeira
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-10-30 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 0
34
+ version: 3.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: database_cleaner
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: ArchiveTree is a Ruby Gem that makes it easy for you to create beautiful chronological archive trees of your models. For instance, you can create a tree for your blog posts.
64
+ email:
65
+ - mail@gnomeslab.com
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - lib/archive_tree/action_view_extensions/draw_archive_tree.rb
74
+ - lib/archive_tree/action_view_extensions.rb
75
+ - lib/archive_tree/core.rb
76
+ - lib/archive_tree.rb
77
+ - MIT-LICENSE
78
+ - README.md
79
+ has_rdoc: true
80
+ homepage: http://github.com/GnomesLab/archive_tree/
81
+ licenses: []
82
+
83
+ post_install_message:
84
+ rdoc_options: []
85
+
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 1
103
+ - 3
104
+ - 7
105
+ version: 1.3.7
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.3.7
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Creates chronological trees of your models based on their created_at column value.
113
+ test_files: []
114
+