herd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Herd Changelog
2
+
3
+ ## v0.0.1
4
+
5
+ Initial Release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 beerlington
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Herd
2
+
3
+ Organize ActiveRecord collection functionality into manageable classes.
4
+
5
+ ## Requirements
6
+
7
+ * Ruby >= 1.9.2
8
+ * Rails >= 3.0.0
9
+
10
+ ## What is Herd?
11
+
12
+ As models grow in size and complexity, it can be challenging to keep
13
+ track of which class methods are for collections and which are not.
14
+ Throw validations, associations, and scopes into the mix, and the model
15
+ can become unwieldy to manage.
16
+
17
+ Herd provides a way to organize collection related functionality into
18
+ separate, manageable classes.
19
+
20
+ Take the following ActiveRecord class for example:
21
+
22
+ ```ruby
23
+ class Movie < ActiveRecord::Base
24
+ scope :recent, where('released_at > ?', 6.months.ago)
25
+ scope :profitable, where('gross > budget')
26
+
27
+ def self.filter_by_title(title)
28
+ where('title LIKE ?', "%#{title}%")
29
+ end
30
+
31
+ def release_year
32
+ released_at.year
33
+ end
34
+
35
+ def profitable?
36
+ gross > budget
37
+ end
38
+ end
39
+ ```
40
+
41
+ Using Herd, you can define a separate class to declare collection concerns:
42
+
43
+ ```ruby
44
+ class Movie < ActiveRecord::Base
45
+ def release_year
46
+ released_at.year
47
+ end
48
+
49
+ def profitable?
50
+ gross > budget
51
+ end
52
+ end
53
+
54
+ class Movies < Herd::Base
55
+ scope :recent, where('released_at > ?', 6.months.ago)
56
+ scope :profitable, where('gross > budget')
57
+
58
+ def self.filter_by_title(title)
59
+ where('title LIKE ?', "%#{title}%")
60
+ end
61
+ end
62
+ ```
63
+
64
+ ## Installation
65
+
66
+ Add this line to your application's Gemfile:
67
+
68
+ gem 'herd'
69
+
70
+ And then execute:
71
+
72
+ $ bundle
73
+
74
+ Or install it yourself as:
75
+
76
+ $ gem install herd
77
+
78
+ You also need to preload all the Herd collections. The simplest way to
79
+ do this is to add a Rails initializer with the following:
80
+
81
+ config/initializers/herd.rb
82
+
83
+ ```ruby
84
+ Herd.load_collections
85
+ ```
86
+
87
+ ## Usage Example
88
+
89
+ Using Herd is as easy as inheriting from `Herd::Base`. Declaring the
90
+ model is optional if it can be inferred by the ActiveRecord class
91
+ name.
92
+
93
+ ```ruby
94
+ class Movie < ActiveRecord::Base
95
+ has_and_belongs_to_many :directors
96
+ has_many :characters, :dependent => :destroy
97
+ end
98
+
99
+ class Movies < Herd::Base
100
+ # Optional
101
+ model Movie
102
+
103
+ scope :failures, where("revenue < '10000000'")
104
+
105
+ def self.directed_by(director)
106
+ where(directors: {name: director}).joins(:directors)
107
+ end
108
+ end
109
+ ```
110
+
111
+ ## Contributing
112
+
113
+ 1. Fork it
114
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
115
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
116
+ 4. Push to the branch (`git push origin my-new-feature`)
117
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/herd.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/herd/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Peter Brown"]
6
+ gem.email = ["github@lette.us"]
7
+ gem.description = %q{Inspired by Backbone.js, this utility lets you define collections for better separation of concerns}
8
+ gem.summary = %q{Organize ActiveRecord collection functionality into separate classes}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "herd"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Herd::VERSION
16
+
17
+ gem.add_dependency('rails', '>= 3.0')
18
+
19
+ gem.add_development_dependency('rake')
20
+ gem.add_development_dependency('rspec-rails', '~> 2.10.0')
21
+ gem.add_development_dependency('sqlite3', '~> 1.3.6')
22
+
23
+ end
@@ -0,0 +1,3 @@
1
+ module Herd
2
+ VERSION = "0.0.1"
3
+ end
data/lib/herd.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "herd/version"
2
+
3
+ module Herd
4
+
5
+ # Load collection classes from a Rails initializer
6
+ # @param [String] path
7
+ def self.load_collections(path=Rails.root.join('app/collections'))
8
+ Dir.glob(File.join(path, '*.rb')).each {|f| require f }
9
+ end
10
+
11
+ class Base
12
+ class_attribute :model_class
13
+
14
+ # Specify the model class that the collection contains
15
+ # @param [Class] model the class that the collection contains
16
+ #
17
+ # @example
18
+ # # ActiveRecord model
19
+ # class Movie < ActiveRecord::Base
20
+ # end
21
+ #
22
+ # # Herd collection
23
+ # class Movies < Herd::Base
24
+ # model Movie
25
+ # end
26
+ def self.model(model)
27
+ self.model_class = model
28
+ end
29
+
30
+ # Add all class methods to the model class so that these methods are
31
+ # available to ActiveRecord::Relation etc
32
+ def self.inherited(klass)
33
+ klass.class_eval do
34
+ def self.singleton_method_added(method_name)
35
+ return if model_class.nil? || method_name == :model_class
36
+
37
+ method = self.method(method_name).to_proc
38
+ model_class.define_singleton_method(method_name, method)
39
+ end
40
+
41
+ # Set the model class name
42
+ begin
43
+ self.model_class = name.singularize.constantize
44
+ rescue NameError
45
+ end
46
+ end
47
+ end
48
+
49
+ # Delegate all methods to the model class if you want to do something like:
50
+ # Collection.find_by_name('blah')
51
+ def self.method_missing(method, *args, &block)
52
+ if model_class.respond_to?(method)
53
+ model_class.send(method, *args, &block)
54
+ else
55
+ super
56
+ end
57
+ end
58
+ end
59
+ end
data/spec/herd_spec.rb ADDED
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Movies do
4
+ let!(:movie1) { Movie.create!(:name => 'The Goonies', :revenue => 100_000_000) }
5
+ let!(:movie2) { Movie.create!(:name => 'Gigli', :revenue => 0) }
6
+
7
+
8
+ after do
9
+ Movie.destroy_all
10
+ Director.destroy_all
11
+ Character.destroy_all
12
+ end
13
+
14
+ it 'should delegate class methods to model' do
15
+ Movies.find_by_name('The Goonies').should == movie1
16
+ end
17
+
18
+ it 'should delegate scope method to model' do
19
+ Movies.failures.should == [movie2]
20
+ end
21
+
22
+ it 'should delegate class methods to active relation class' do
23
+ chunk = Character.create!(name: 'chunk')
24
+ movie1.characters << chunk
25
+
26
+ movie1.characters.fat.should == [chunk]
27
+ end
28
+
29
+ it 'chain a bunch of stuff together and still work' do
30
+ chunk = Character.create!(name: 'chunk')
31
+ rdonner = Director.create!(:name => 'Richard Donner')
32
+ movie1.characters << chunk
33
+ movie1.directors << rdonner
34
+
35
+ Movies.directed_by('Richard Donner').first.characters.fat.should == [chunk]
36
+ end
37
+
38
+ end
39
+
40
+ describe Directors do
41
+ it 'should set the model name implicitly from the collection name' do
42
+ Directors.create!(:name => 'Richard Donner')
43
+
44
+ Directors.all.should have(1).director
45
+ end
46
+
47
+ it 'should raise an error if method cannot be found' do
48
+ -> { Directors.no_method }.should raise_error(NoMethodError)
49
+ end
50
+ end
@@ -0,0 +1,67 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'rails/all'
6
+ require 'rspec/rails'
7
+ require 'herd'
8
+
9
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
10
+
11
+ RSpec.configure do |config|
12
+ config.color_enabled = true
13
+ config.use_transactional_fixtures = true
14
+ end
15
+
16
+ ActiveRecord::Schema.define(:version => 1) do
17
+ create_table :directors, :force => true do |t|
18
+ t.string :name
19
+ end
20
+
21
+ create_table :directors_movies, :force => true do |t|
22
+ t.integer :director_id
23
+ t.integer :movie_id
24
+ end
25
+
26
+ create_table :movies, :force => true do |t|
27
+ t.string :name
28
+ t.integer :revenue
29
+ end
30
+
31
+ create_table :characters, :force => true do |t|
32
+ t.string :name
33
+ t.integer :movie_id
34
+ end
35
+ end
36
+
37
+ class Movie < ActiveRecord::Base
38
+ has_and_belongs_to_many :directors
39
+ has_many :characters, :dependent => :destroy
40
+ end
41
+
42
+ class Movies < Herd::Base
43
+ model Movie
44
+
45
+ scope :failures, where("revenue < '10000000'")
46
+
47
+ def self.directed_by(director)
48
+ where(directors: {name: director}).joins(:directors)
49
+ end
50
+ end
51
+
52
+ class Character < ActiveRecord::Base
53
+ belongs_to :movie
54
+ end
55
+
56
+ class Characters < Herd::Base
57
+ model Character
58
+
59
+ scope :fat, where(name: 'chunk')
60
+ end
61
+
62
+ class Director < ActiveRecord::Base
63
+ has_and_belongs_to_many :movies
64
+ end
65
+
66
+ class Directors < Herd::Base
67
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: herd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Peter Brown
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec-rails
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.10.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.10.0
62
+ - !ruby/object:Gem::Dependency
63
+ name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.3.6
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.6
78
+ description: Inspired by Backbone.js, this utility lets you define collections for
79
+ better separation of concerns
80
+ email:
81
+ - github@lette.us
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - CHANGELOG.md
88
+ - Gemfile
89
+ - LICENSE
90
+ - README.md
91
+ - Rakefile
92
+ - herd.gemspec
93
+ - lib/herd.rb
94
+ - lib/herd/version.rb
95
+ - spec/herd_spec.rb
96
+ - spec/spec_helper.rb
97
+ homepage: ''
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ segments:
110
+ - 0
111
+ hash: -4311655531898936104
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: -4311655531898936104
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.19
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Organize ActiveRecord collection functionality into separate classes
127
+ test_files:
128
+ - spec/herd_spec.rb
129
+ - spec/spec_helper.rb