groupy 0.2.1

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.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 [Matthew Rudy Jacobs]
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 ADDED
@@ -0,0 +1,92 @@
1
+ Groupy
2
+ =======
3
+
4
+ Categorise records into nested groups of values.
5
+
6
+ Example
7
+ =======
8
+
9
+ Define our groups
10
+
11
+ class Food
12
+ property :dish, String
13
+
14
+ include Groupy
15
+ groupy :dish do
16
+ group :healthy do
17
+ group :fruit do
18
+ value :apple
19
+ value :orange
20
+ end
21
+ value :rice
22
+ end
23
+
24
+ group :unhealthy do
25
+ value :fried_egg
26
+ value :bacon
27
+ end
28
+ end
29
+ end
30
+
31
+ We can then ask a particular food:
32
+
33
+ apple.healthy?
34
+ apple.fruit?
35
+ apple.apple?
36
+
37
+ And we can scope the class by any of these groups
38
+
39
+ Food.apples.count
40
+ Food.fruits.all
41
+
42
+ We also get a magic "all_" method
43
+
44
+ Food.all_dishes
45
+
46
+ You can also tell groupy to add the column name as a suffix
47
+
48
+ class Something
49
+ groupy :size, :suffix => true do
50
+ value :small
51
+ value :medium
52
+ value :large
53
+ end
54
+ end
55
+
56
+ something.small_size?
57
+
58
+ Or you may want to store all the values as a conveniently named constant.
59
+
60
+ class Something
61
+ groupy :size, :constants => true do
62
+ value :small
63
+ value :medium
64
+ value :large
65
+ end
66
+ end
67
+
68
+ Something::SMALL
69
+ Something::MEDIUM
70
+ Something::LARGE
71
+
72
+ Or of course you can do both together
73
+
74
+ Something::SMALL_SIZE
75
+ Something::MEDIUM_SIZE
76
+ Something::LARGE_SIZE
77
+
78
+ Any values will be lowercased and underscored
79
+
80
+ class Something
81
+ groupy :type do
82
+ value :SomeClass
83
+ value :"Namespace::KlassName"
84
+ end
85
+ end
86
+
87
+ something.some_class?
88
+ something.namespace_klass_name?
89
+
90
+ B00m!
91
+
92
+ Copyright (c) 2010 [Matthew Rudy Jacobs], released under the MIT license
@@ -0,0 +1,85 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test Groupy.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for Groupy.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Groupy'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ require "rake/gempackagetask"
26
+
27
+ # This builds the actual gem. For details of what all these options
28
+ # mean, and other ones you can add, check the documentation here:
29
+ #
30
+ # http://rubygems.org/read/chapter/20
31
+ #
32
+ spec = Gem::Specification.new do |s|
33
+
34
+ # Change these as appropriate
35
+ s.name = "groupy"
36
+ s.version = "0.2.1"
37
+ s.summary = "Categorise Active Records in nested groups with magical scopes, ? methods, and constants."
38
+ s.author = "Matthew Rudy Jacobs"
39
+ s.email = "MatthewRudyJacobs@gmail.com"
40
+ s.homepage = "https://github.com/matthewrudy/groupy"
41
+
42
+ s.has_rdoc = true
43
+ s.extra_rdoc_files = %w(README)
44
+ s.rdoc_options = %w(--main README)
45
+
46
+ # Add any extra files to include in the gem
47
+ s.files = %w(README MIT-LICENSE Rakefile) + Dir.glob("{test,lib}/**/*")
48
+ s.require_paths = ["lib"]
49
+
50
+ # If you want to depend on other gems, add them here, along with any
51
+ # relevant versions
52
+ s.add_dependency("activesupport")
53
+
54
+ # If your tests use any gems, include them here
55
+ # s.add_development_dependency("mocha") # for example
56
+ end
57
+
58
+ # This task actually builds the gem. We also regenerate a static
59
+ # .gemspec file, which is useful if something (i.e. GitHub) will
60
+ # be automatically building a gem for this project. If you're not
61
+ # using GitHub, edit as appropriate.
62
+ #
63
+ # To publish your gem online, install the 'gemcutter' gem; Read more
64
+ # about that here: http://gemcutter.org/pages/gem_docs
65
+ Rake::GemPackageTask.new(spec) do |pkg|
66
+ pkg.gem_spec = spec
67
+ end
68
+
69
+ desc "Build the gemspec file #{spec.name}.gemspec"
70
+ task :gemspec do
71
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
72
+ File.open(file, "w") {|f| f << spec.to_ruby }
73
+ end
74
+
75
+ # If you don't want to generate the .gemspec file, just remove this line. Reasons
76
+ # why you might want to generate a gemspec:
77
+ # - using bundler with a git source
78
+ # - building the gem without rake (i.e. gem build blah.gemspec)
79
+ # - maybe others?
80
+ task :package => :gemspec
81
+
82
+ desc 'Clear out RDoc and generated packages'
83
+ task :clean => [:clobber_rdoc, :clobber_package] do
84
+ rm "#{spec.name}.gemspec"
85
+ end
@@ -0,0 +1,134 @@
1
+ require 'active_support/core_ext/class/attribute_accessors'
2
+
3
+ module Groupy
4
+
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+
9
+ cattr_accessor :groupies
10
+ self.groupies = {}
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ def groupy(column, options={}, &block)
17
+ container = OuterGroup.new(&block)
18
+ container.attach!(self, column, options)
19
+ self.groupies[column] = container
20
+ end
21
+
22
+ end
23
+
24
+ class Group
25
+
26
+ def initialize(name, &block)
27
+ @name = name
28
+ @sub_groups = []
29
+
30
+ instance_eval(&block)
31
+ end
32
+ attr_reader :name, :sub_groups
33
+
34
+ def values
35
+ value_groups.map{|g| g.value}
36
+ end
37
+
38
+ def value_groups
39
+ self.sub_groups.inject([]) do |array, sg|
40
+ if sg.is_a?(Value)
41
+ array << sg
42
+ else
43
+ array += sg.value_groups
44
+ end
45
+ end
46
+ end
47
+
48
+ def subgroups
49
+ self.sub_groups.inject({}) do |hash, g|
50
+ if g.respond_to?(:subgroups)
51
+ hash.merge!(g.subgroups)
52
+ end
53
+ hash[g.name] = g.values
54
+ hash
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def group(name, &block)
61
+ subgroup = Group.new(name, &block)
62
+ self.sub_groups << subgroup
63
+ end
64
+
65
+ def value(name)
66
+ self.sub_groups << Value.new(name)
67
+ end
68
+
69
+ end
70
+
71
+ class OuterGroup < Group
72
+
73
+ def initialize(&block)
74
+ super(:all, &block)
75
+ end
76
+
77
+ def attach!(klass, column_name, options)
78
+ self.subgroups.each do |group_name, group_values|
79
+
80
+ method_name = if options[:suffix]
81
+ "#{group_name}_#{column_name}"
82
+ else
83
+ group_name
84
+ end
85
+
86
+ klass.class_eval <<-RUBY
87
+ def #{method_name}?
88
+ #{group_values.inspect}.include?(self.#{column_name})
89
+ end
90
+ RUBY
91
+
92
+ scope_name = method_name.to_s.pluralize
93
+
94
+ if defined?(ActiveRecord) && klass < ActiveRecord::Base
95
+ klass.scope(scope_name, klass.where(column_name => group_values))
96
+ end
97
+ end
98
+
99
+ if options[:constants]
100
+ self.value_groups.each do |value_group|
101
+
102
+ constant_name = if options[:suffix]
103
+ "#{value_group.name}_#{column_name}"
104
+ else
105
+ value_group.name
106
+ end
107
+ klass.const_set("#{constant_name.upcase}", value_group.value)
108
+ end
109
+ end
110
+
111
+ klass.class_eval <<-RUBY
112
+ def self.all_#{column_name.to_s.pluralize}
113
+ self.groupies[#{column_name.inspect}].values
114
+ end
115
+ RUBY
116
+ end
117
+
118
+ end
119
+
120
+ class Value
121
+
122
+ def initialize(name)
123
+ @name = name.to_s.gsub("::", "").underscore.to_sym
124
+ @value = name.to_s.freeze
125
+ end
126
+ attr_reader :name, :value
127
+
128
+ def values
129
+ [value]
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,103 @@
1
+ require 'test_helper'
2
+
3
+ require 'groupy'
4
+
5
+ class GroupyTest < ActiveSupport::TestCase
6
+
7
+ class Food
8
+
9
+ def initialize(dish)
10
+ @dish = dish
11
+ @spiciness = "small"
12
+ end
13
+ attr_reader :dish
14
+ attr_reader :spiciness
15
+
16
+ include Groupy
17
+ groupy :dish do
18
+ group :healthy do
19
+ group :fruit do
20
+ value :apple
21
+ value :orange
22
+ end
23
+ value :rice
24
+ end
25
+
26
+ group :unhealthy do
27
+ value :fried_egg
28
+ value :bacon
29
+ end
30
+ end
31
+
32
+ # we can have multiple groupies
33
+ groupy :spiciness, :suffix => true, :constants => true do
34
+ group :wussy do
35
+ value :small
36
+ end
37
+ value :extreme
38
+ end
39
+
40
+ end
41
+
42
+ test "groupies" do
43
+ groupy = Food.groupies[:dish]
44
+ assert_instance_of Groupy::OuterGroup, groupy
45
+ end
46
+
47
+ test "subgroups" do
48
+ assert_equal [:healthy, :fruit, :apple, :orange, :rice, :unhealthy, :fried_egg, :bacon].sort_by(&:to_s), Food.groupies[:dish].subgroups.keys.sort_by(&:to_s)
49
+ assert_equal ["apple", "orange", "rice"], Food.groupies[:dish].subgroups[:healthy]
50
+ assert_equal ["apple", "orange"], Food.groupies[:dish].subgroups[:fruit]
51
+ assert_equal ["apple"], Food.groupies[:dish].subgroups[:apple]
52
+ end
53
+
54
+ test "all_" do
55
+ assert_equal ["apple", "orange", "rice", "fried_egg", "bacon"].sort, Food.all_dishes.sort
56
+ end
57
+
58
+ test "? methods" do
59
+ orange = Food.new("orange")
60
+ assert orange.healthy?
61
+ assert !orange.rice?
62
+ assert orange.fruit?
63
+ assert orange.orange?
64
+
65
+ # and the second groupy
66
+ assert orange.wussy_spiciness?
67
+ assert orange.small_spiciness?
68
+ assert !orange.extreme_spiciness?
69
+ end
70
+
71
+ test "constants" do
72
+ assert_equal "small", Food::SMALL_SPICINESS
73
+ assert_equal "extreme", Food::EXTREME_SPICINESS
74
+ end
75
+
76
+ class Thing
77
+ include Groupy
78
+
79
+ def initialize(type)
80
+ @type = type
81
+ end
82
+ attr_reader :type
83
+
84
+ groupy :type do
85
+ group :number do
86
+ value :FixNum
87
+ value "My::FixNum"
88
+ end
89
+ value :String
90
+ end
91
+ end
92
+
93
+ test "underscore value methods" do
94
+ number = Thing.new("My::FixNum")
95
+ assert number.number?
96
+ assert !number.fix_num?
97
+ assert number.my_fix_num?
98
+ assert !number.string?
99
+ end
100
+
101
+ # TODO: setup an AR test
102
+
103
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
4
+
5
+ $:.unshift File.expand_path(File.dirname(__FILE__)+"/../lib")
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ require 'groupy'
4
+ require 'active_record'
5
+
6
+ ActiveRecord::Base.establish_connection(
7
+ :adapter => "sqlite3",
8
+ :dbfile => ":memory:",
9
+ :database => "groupy_test"
10
+ )
11
+
12
+ ActiveRecord::Schema.define(:version => 0) do
13
+ create_table :somethings, :force => true do |t|
14
+ t.string :size
15
+ end
16
+ end
17
+
18
+ class Something < ActiveRecord::Base
19
+
20
+ include Groupy
21
+
22
+ groupy :size, :suffix => true do
23
+ value :small
24
+ value :medium
25
+ value :large
26
+ end
27
+ end
28
+
29
+ class WithDatabaseTest < ActiveSupport::TestCase
30
+
31
+ test "scopes" do
32
+ assert_equal [], Something.small_sizes.all
33
+ assert_equal 0, Something.small_sizes.count
34
+ assert_equal [], Something.medium_sizes.all
35
+ assert_equal 0, Something.medium_sizes.count
36
+
37
+ small1 = Something.create!(:size => "small")
38
+ medium = Something.create!(:size => "medium")
39
+ small2 = Something.create!(:size => "small")
40
+
41
+ assert_equal [small1, small2], Something.small_sizes.all
42
+ assert_equal 2, Something.small_sizes.count
43
+ assert_equal [medium], Something.medium_sizes.all
44
+ assert_equal 1, Something.medium_sizes.count
45
+ end
46
+
47
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: groupy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 1
10
+ version: 0.2.1
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Rudy Jacobs
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-14 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description:
36
+ email: MatthewRudyJacobs@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ files:
44
+ - README
45
+ - MIT-LICENSE
46
+ - Rakefile
47
+ - test/groupy_test.rb
48
+ - test/test_helper.rb
49
+ - test/with_database_test.rb
50
+ - lib/groupy.rb
51
+ has_rdoc: true
52
+ homepage: https://github.com/matthewrudy/groupy
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --main
58
+ - README
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ requirements: []
80
+
81
+ rubyforge_project:
82
+ rubygems_version: 1.5.2
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Categorise Active Records in nested groups with magical scopes, ? methods, and constants.
86
+ test_files: []
87
+