groupy 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+