herd 0.0.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.
- data/.gitignore +17 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +117 -0
- data/Rakefile +2 -0
- data/herd.gemspec +23 -0
- data/lib/herd/version.rb +3 -0
- data/lib/herd.rb +59 -0
- data/spec/herd_spec.rb +50 -0
- data/spec/spec_helper.rb +67 -0
- metadata +129 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
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
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
|
data/lib/herd/version.rb
ADDED
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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|