active_presenter 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/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
+