active_presenter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Daniel Haran, James Golick, GiraffeSoft, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,75 @@
1
+ = ActivePresenter
2
+
3
+ ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)
4
+
5
+ By acting nearly identically to ActiveRecord models, ActivePresenter makes presenters highly approachable to anybody who is already familiar with ActiveRecord.
6
+
7
+ == Get It
8
+
9
+ As a gem:
10
+
11
+ $ sudo gem install active_presenter
12
+
13
+ As a rails gem dependency:
14
+
15
+ config.gem 'active_presenter'
16
+
17
+ Or get the source from github:
18
+
19
+ $ git clone git://github.com/giraffesoft/active_presenter.git
20
+
21
+ (or fork it at http://github.com/giraffesoft/active_presenter)
22
+
23
+ == Usage
24
+
25
+ Creating a presenter is as simple as subclassing ActivePresenter::Base. Use the presents method to indicate which models the presenter should present.
26
+
27
+ class SignupPresenter < ActivePresenter::Base
28
+ presents User, Account
29
+ end
30
+
31
+ === Instantiation
32
+
33
+ Then, you can instantiate the presenter using either, or both of two forms.
34
+
35
+ For example, if you had a SignupPresenter that presented User, and Account, you could specify arguments in the following two forms:
36
+
37
+ 1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
38
+
39
+ - This form is useful for initializing a new presenter from the params hash: i.e. SignupPresenter.new(params[:signup_presenter])
40
+
41
+ 2. SignupPresenter.new(:user => User.find(1), :account => Account.find(2))
42
+
43
+ - This form is useful if you have instances that you'd like to edit using the presenter. You can subsequently call presenter.update_attributes(params[:signup_presenter]) just like with a regular AR instance.
44
+
45
+ Both forms can also be mixed together: SignupPresenter.new(:user => User.find(1), :user_login => 'james').
46
+
47
+ In this case, the login attribute will be updated on the user instance provided.
48
+
49
+ If you don't specify an instance, one will be created by calling Model.new
50
+
51
+ === Validation
52
+
53
+ The #valid? method will return true or false based on the validity of the presented objects.
54
+
55
+ This is calculated by calling #valid? on them.
56
+
57
+ You can retrieve the errors in two ways.
58
+
59
+ 1. By calling #errors on the presenter, which returns an instance of ActiveRecord::Errors where all the attributes are in type_name_attribute_name form (i.e. You'd retrieve an error on User#login, by calling @presenter.errors.on(:user_login)).
60
+
61
+ 2. By calling @presenter.user_errors, or @presenter.user.errors to retrieve the errors from one presentable.
62
+
63
+ Both of these methods are compatible with error_messages_for. It just depends whether you'd like to show all the errors in one block, or whether you'd prefer to break them up.
64
+
65
+ === Saving
66
+
67
+ You can save your presenter the same way you'd save an ActiveRecord object. Both #save, and #save! behave the same way they do on a normal AR model.
68
+
69
+ == Credits
70
+
71
+ ActivePresenter was created, and is maintained by {Daniel Haran}[http://danielharan.com] and {James Golick}[http://jamesgolick.com] on the train ride to {RubyFringe}[http://rubyfringe.com] from Montreal.
72
+
73
+ == License
74
+
75
+ ActivePresenter is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require File.dirname(__FILE__)+'/lib/active_presenter'
4
+ Dir.glob(File.dirname(__FILE__)+'/lib/tasks/**/*.rake').each { |l| load l }
5
+
6
+ task :default => :test
7
+
8
+ task :test do
9
+ Dir['test/**/*_test.rb'].each { |l| require l }
10
+ end
11
+
12
+ desc 'Generate documentation for the ResourceController plugin.'
13
+ Rake::RDocTask.new(:rdoc) do |rdoc|
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = 'ActivePresenter'
16
+ rdoc.options << '--line-numbers' << '--inline-source'
17
+ rdoc.rdoc_files.include('README')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ Dir.glob(File.dirname(__FILE__)+'/active_presenter/**/*.rb').each { |l| require l }
4
+
5
+ module ActivePresenter
6
+ NAME = 'active_presenter'
7
+ end
@@ -0,0 +1,165 @@
1
+ module ActivePresenter
2
+ # Base class for presenters. See README for usage.
3
+ #
4
+ class Base
5
+ class_inheritable_accessor :presented
6
+ self.presented = {}
7
+
8
+ # Indicates which models are to be presented by this presenter.
9
+ # i.e.
10
+ #
11
+ # class SignupPresenter < ActivePresenter::Base
12
+ # presents User, Account
13
+ # end
14
+ #
15
+ #
16
+ def self.presents(*types)
17
+ attr_accessor *types
18
+
19
+ types.each do |t|
20
+ define_method("#{t}_errors") do
21
+ send(t).errors
22
+ end
23
+
24
+ presented[t] = t.to_s.classify.constantize
25
+ end
26
+ end
27
+
28
+ def self.human_attribute_name(attribute_name)
29
+ presentable_type = presented.keys.detect do |type|
30
+ attribute_name.to_s.starts_with?("#{type}_")
31
+ end
32
+
33
+ attribute_name.to_s.gsub("#{presentable_type}_", "").humanize
34
+ end
35
+
36
+ attr_accessor :errors
37
+
38
+ # Accepts arguments in two forms. For example, if you had a SignupPresenter that presented User, and Account, you could specify arguments in the following two forms:
39
+ #
40
+ # 1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
41
+ # - This form is useful for initializing a new presenter from the params hash: i.e. SignupPresenter.new(params[:signup_presenter])
42
+ # 2. SignupPresenter.new(:user => User.find(1), :account => Account.find(2))
43
+ # - This form is useful if you have instances that you'd like to edit using the presenter. You can subsequently call presenter.update_attributes(params[:signup_presenter]) just like with a regular AR instance.
44
+ #
45
+ # Both forms can also be mixed together: SignupPresenter.new(:user => User.find(1), :user_login => 'james')
46
+ # In this case, the login attribute will be updated on the user instance provided.
47
+ #
48
+ # If you don't specify an instance, one will be created by calling Model.new
49
+ #
50
+ def initialize(args = {})
51
+ presented.each do |type, klass|
52
+ send("#{type}=", args[type].is_a?(klass) ? args.delete(type) : klass.new)
53
+ end
54
+
55
+ self.attributes = args
56
+ end
57
+
58
+ # Set the attributes of the presentable instances using the type_attribute form (i.e. user_login => 'james')
59
+ #
60
+ def attributes=(attrs)
61
+ attrs.each { |k,v| send("#{k}=", v) }
62
+ end
63
+
64
+ # Makes sure that the presenter is accurate about responding to presentable's attributes, even though they are handled by method_missing.
65
+ #
66
+ def respond_to?(method)
67
+ presented_attribute?(method) || super
68
+ end
69
+
70
+ # Handles the decision about whether to delegate getters and setters to presentable instances.
71
+ #
72
+ def method_missing(method_name, *args, &block)
73
+ presented_attribute?(method_name) ? delegate_message(method_name, *args, &block) : super
74
+ end
75
+
76
+ # Returns an instance of ActiveRecord::Errors with all the errors from the presentables merged in using the type_attribute form (i.e. user_login).
77
+ #
78
+ def errors
79
+ @errors ||= ActiveRecord::Errors.new(self)
80
+ end
81
+
82
+ # Returns boolean based on the validity of the presentables by calling valid? on each of them.
83
+ #
84
+ def valid?
85
+ presented.keys.each do |type|
86
+ presented_inst = send(type)
87
+
88
+ merge_errors(presented_inst, type) unless presented_inst.valid?
89
+ end
90
+
91
+ errors.empty?
92
+ end
93
+
94
+ # Save all of the presentables, wrapped in a transaction.
95
+ #
96
+ # Returns true or false based on success.
97
+ #
98
+ def save
99
+ saved = false
100
+
101
+ ActiveRecord::Base.transaction do
102
+ if valid?
103
+ saved = presented_instances.map { |i| i.save(false) }.all?
104
+ raise ActiveRecord::Rollback unless saved # TODO: Does this happen implicitly?
105
+ end
106
+ end
107
+
108
+ saved
109
+ end
110
+
111
+ # Save all of the presentables, by calling each of their save! methods, wrapped in a transaction.
112
+ #
113
+ # Returns true on success, will raise otherwise.
114
+ #
115
+ def save!
116
+ ActiveRecord::Base.transaction do
117
+ valid? # collect errors before potential exception raise
118
+ presented_instances.each { |i| i.save! }
119
+ end
120
+ end
121
+
122
+ # Update attributes, and save the presentables
123
+ #
124
+ # Returns true or false based on success.
125
+ #
126
+ def update_attributes(attrs)
127
+ self.attributes = attrs
128
+ save
129
+ end
130
+
131
+ protected
132
+ def presented_instances
133
+ presented.keys.map { |key| send(key) }
134
+ end
135
+
136
+ def delegate_message(method_name, *args, &block)
137
+ presentable = presentable_for(method_name)
138
+ send(presentable).send(flatten_attribute_name(method_name, presentable), *args, &block)
139
+ end
140
+
141
+ def presentable_for(method_name)
142
+ presented.keys.detect do |type|
143
+ method_name.to_s.starts_with?(attribute_prefix(type))
144
+ end
145
+ end
146
+
147
+ def presented_attribute?(method_name)
148
+ !presentable_for(method_name).nil?
149
+ end
150
+
151
+ def flatten_attribute_name(name, type)
152
+ name.to_s.gsub(/^#{attribute_prefix(type)}/, '')
153
+ end
154
+
155
+ def attribute_prefix(type)
156
+ "#{type}_"
157
+ end
158
+
159
+ def merge_errors(presented_inst, type)
160
+ presented_inst.errors.each do |att,msg|
161
+ errors.add(attribute_prefix(type)+att, msg)
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,9 @@
1
+ module ActivePresenter
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,62 @@
1
+ require 'rake/gempackagetask'
2
+
3
+ task :clean => :clobber_package
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = ActivePresenter::NAME
7
+ s.version = ActivePresenter::VERSION::STRING
8
+ s.summary =
9
+ s.description = "ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)"
10
+ s.author = "James Golick & Daniel Haran"
11
+ s.email = 'james@giraffesoft.ca'
12
+ s.homepage = 'http://jamesgolick.com/active_presenter'
13
+ s.rubyforge_project = 'active_presenter'
14
+ s.has_rdoc = true
15
+
16
+ s.required_ruby_version = '>= 1.8.5'
17
+
18
+ s.files = %w(README LICENSE Rakefile) +
19
+ Dir.glob("{lib,test}/**/*")
20
+
21
+ s.require_path = "lib"
22
+ end
23
+
24
+ Rake::GemPackageTask.new(spec) do |p|
25
+ p.gem_spec = spec
26
+ end
27
+
28
+ task :tag_warn do
29
+ puts "*" * 40
30
+ puts "Don't forget to tag the release:"
31
+ puts
32
+ puts " git tag -a v#{ActivePresenter::VERSION::STRING}"
33
+ puts
34
+ puts "or run rake tag"
35
+ puts "*" * 40
36
+ end
37
+
38
+ task :tag do
39
+ sh "git tag -a v#{ActivePresenter::VERSION::STRING}"
40
+ end
41
+ task :gem => :tag_warn
42
+
43
+ namespace :gem do
44
+ namespace :upload do
45
+
46
+ desc 'Upload gems (ruby & win32) to rubyforge.org'
47
+ task :rubyforge => :gem do
48
+ sh 'rubyforge login'
49
+ sh "rubyforge add_release giraffesoft active_presenter #{ActivePresenter::VERSION::STRING} pkg/#{spec.full_name}.gem"
50
+ sh "rubyforge add_file giraffesoft active_presenter #{ActivePresenter::VERSION::STRING} pkg/#{spec.full_name}.gem"
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ task :install => [:clobber, :package] do
57
+ sh "sudo gem install pkg/#{spec.full_name}.gem"
58
+ end
59
+
60
+ task :uninstall => :clean do
61
+ sh "sudo gem uninstall -v #{ActivePresenter::VERSION::STRING} -x #{ActivePresenter::NAME}"
62
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__)+'/test_helper'
2
+
3
+ Expectations do
4
+ expect :user => User, :account => Account do
5
+ SignupPresenter.presented
6
+ end
7
+
8
+ expect User.create!(hash_for_user) do |u|
9
+ SignupPresenter.new(:user => u.expected).user
10
+ end
11
+
12
+ expect User do
13
+ SignupPresenter.new.user
14
+ end
15
+
16
+ expect User.any_instance.to.receive(:login=).with('james') do
17
+ SignupPresenter.new(:user_login => 'james')
18
+ end
19
+
20
+ expect 'mymockvalue' do
21
+ User.any_instance.stubs(:login).returns('mymockvalue')
22
+ SignupPresenter.new.user_login
23
+ end
24
+
25
+ expect User.any_instance.to.receive(:login=).with('mymockvalue') do
26
+ SignupPresenter.new.user_login = 'mymockvalue'
27
+ end
28
+
29
+ expect SignupPresenter.new.not.to.be.valid?
30
+ expect SignupPresenter.new(:user => User.new(hash_for_user)).to.be.valid?
31
+
32
+ expect ActiveRecord::Errors do
33
+ s = SignupPresenter.new
34
+ s.valid?
35
+ s.errors
36
+ end
37
+
38
+ expect ActiveRecord::Errors do
39
+ s = SignupPresenter.new
40
+ s.valid?
41
+ s.user_errors
42
+ end
43
+
44
+ expect ActiveRecord::Errors do
45
+ s = SignupPresenter.new
46
+ s.valid?
47
+ s.account_errors
48
+ end
49
+
50
+ expect String do
51
+ s = SignupPresenter.new
52
+ s.valid?
53
+ s.errors.on(:user_login)
54
+ end
55
+
56
+ expect ActiveRecord::Base.to.receive(:transaction) do
57
+ s = SignupPresenter.new
58
+ s.save
59
+ end
60
+
61
+ expect User.any_instance.to.receive(:save) do
62
+ s = SignupPresenter.new :user => User.new(hash_for_user)
63
+ s.save
64
+ end
65
+
66
+ expect Account.any_instance.to.receive(:save) do
67
+ s = SignupPresenter.new :user => User.new(hash_for_user)
68
+ s.save
69
+ end
70
+
71
+ expect SignupPresenter.new.not.to.be.save
72
+
73
+ expect ActiveRecord::Rollback do
74
+ ActiveRecord::Base.stubs(:transaction).yields
75
+ User.any_instance.stubs(:save).returns(false)
76
+ Account.any_instance.stubs(:save).returns(false)
77
+ s = SignupPresenter.new :user => User.new(hash_for_user)
78
+ s.save
79
+ end
80
+
81
+ expect ActiveRecord::Base.to.receive(:transaction) do
82
+ s = SignupPresenter.new
83
+ s.save!
84
+ end
85
+
86
+ expect User.any_instance.to.receive(:save!) do
87
+ s = SignupPresenter.new
88
+ s.save!
89
+ end
90
+
91
+ expect Account.any_instance.to.receive(:save!) do
92
+ User.any_instance.stubs(:save!)
93
+ s = SignupPresenter.new
94
+ s.save!
95
+ end
96
+
97
+ expect ActiveRecord::RecordInvalid do
98
+ SignupPresenter.new.save!
99
+ end
100
+
101
+ expect SignupPresenter.new(:user => User.new(hash_for_user)).to.be.save!
102
+
103
+ expect SignupPresenter.new.to.be.respond_to?(:user_login)
104
+ expect SignupPresenter.new.to.be.respond_to?(:valid?) # just making sure i didn't break everything :)
105
+
106
+ expect User.create!(hash_for_user).not.to.be.login_changed? do |user|
107
+ s = SignupPresenter.new(:user => user)
108
+ s.update_attributes :user_login => 'Something Totally Different'
109
+ end
110
+
111
+ expect SignupPresenter.new(:user => User.create!(hash_for_user)).to.receive(:save) do |s|
112
+ s.update_attributes :user_login => 'Something'
113
+ end
114
+
115
+ expect 'Something Different' do
116
+ s = SignupPresenter.new
117
+ s.update_attributes :user_login => 'Something Different'
118
+ s.user_login
119
+ end
120
+
121
+ # this is a regression test to make sure that _title is working. we had a weird conflict with using String#delete
122
+ expect 'something' do
123
+ s = SignupPresenter.new :account_title => 'something'
124
+ s.account_title
125
+ end
126
+
127
+ expect String do
128
+ s = SignupPresenter.new
129
+ s.save
130
+ s.errors.on(:user_login)
131
+ end
132
+
133
+ expect String do
134
+ s = SignupPresenter.new
135
+ s.save! rescue
136
+ s.errors.on(:user_login)
137
+ end
138
+
139
+ expect 'Login' do
140
+ SignupPresenter.human_attribute_name(:user_login)
141
+ end
142
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__)+'/../lib/active_presenter'
2
+ require 'expectations'
3
+
4
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
5
+ ActiveRecord::Base.establish_connection('sqlite3')
6
+
7
+ ActiveRecord::Schema.define(:version => 0) do
8
+ create_table :users do |t|
9
+ t.boolean :admin, :default => false
10
+ t.string :login, :default => ''
11
+ t.string :password, :default => ''
12
+ end
13
+
14
+ create_table :accounts do |t|
15
+ t.string :subdomain, :default => ''
16
+ t.string :title, :default => ''
17
+ end
18
+ end
19
+
20
+ class User < ActiveRecord::Base
21
+ validates_presence_of :login, :password
22
+ end
23
+ class Account < ActiveRecord::Base; end
24
+
25
+ class SignupPresenter < ActivePresenter::Base
26
+ presents :account, :user
27
+ end
28
+
29
+ def hash_for_user(opts = {})
30
+ {:login => 'jane', :password => 'seekrit' }.merge(opts)
31
+ end
32
+
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_presenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - James Golick & Daniel Haran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-27 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)
17
+ email: james@giraffesoft.ca
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - LICENSE
27
+ - Rakefile
28
+ - lib/active_presenter
29
+ - lib/active_presenter/base.rb
30
+ - lib/active_presenter/version.rb
31
+ - lib/active_presenter.rb
32
+ - lib/tasks
33
+ - lib/tasks/gem.rake
34
+ - test/base_test.rb
35
+ - test/test_helper.rb
36
+ has_rdoc: true
37
+ homepage: http://jamesgolick.com/active_presenter
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.8.5
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project: active_presenter
58
+ rubygems_version: 1.1.1
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: ActivePresenter is the presenter library you already know! (...if you know ActiveRecord)
62
+ test_files: []
63
+