decent_exposure 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,12 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Everyone is permitted to copy and distribute verbatim or modified
5
+ copies of this license document, and changing it is allowed as long
6
+ as the name is changed.
7
+
8
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
9
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
10
+
11
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
12
+
@@ -0,0 +1,106 @@
1
+ DecentExposure
2
+ ==============
3
+
4
+ _Copying over instance variables is bad, mmm-kay?_
5
+
6
+ DecentExposure helps you program to an interface, rather than an implementation in
7
+ your Rails controllers.
8
+
9
+ Sharing state via instance variables in controllers promotes close coupling with
10
+ views. DecentExposure gives you a declarative manner of exposing an interface to the
11
+ state that controllers contain, thereby decreasing coupling and improving your
12
+ testability and overall design.
13
+
14
+ Installation
15
+ ------------
16
+
17
+ gem install decent_exposure
18
+
19
+ Configure your application to use it:
20
+
21
+ In `config/environment.rb`:
22
+
23
+ config.gem 'decent_exposure'
24
+
25
+ The Particulars
26
+ ---------------
27
+
28
+ `expose` creates a method with the given name, evaluates the provided block (or
29
+ intuits a value when no block is passed) and memoizes the result. This method is
30
+ then declared as a `helper_method` so that views may have access to it and is
31
+ made unroutable as an action.
32
+
33
+ Examples
34
+ --------
35
+
36
+ ### In your controllers
37
+
38
+ When no block is given, `expose` attempts to intuit which resource you want to
39
+ acquire:
40
+
41
+ # Category.find(params[:category_id] || params[:id])
42
+ expose(:category)
43
+
44
+ As the example shows, the symbol passed is used to guess the class name of the
45
+ object you want an instance of. Almost every controller has one of these. In the
46
+ RESTful controller paradigm, you might use this in `#show`, `#edit`, `#update`
47
+ or `#destroy`.
48
+
49
+ In the slightly more complicated scenario, you need to find an instance of an
50
+ object which doesn't map cleanly to `Object#find`:
51
+
52
+ expose(:product){ category.products.find(params[:id]) }
53
+
54
+ In the RESTful controller paradigm, you'll again find yourself using this in
55
+ `#show`, `#edit`, `#update` or `#destroy`.
56
+
57
+ When the code has become complex enough to surpass a single line (and is not
58
+ appropriate to extract into a model method), use the `do...end` style of block:
59
+
60
+ expose(:associated_products) do
61
+ product.associated.tap do |associated_products|
62
+ present(associated_products, :with => AssociatedProductPresenter)
63
+ end
64
+ end
65
+
66
+ ### In your views
67
+
68
+ Use the product of those assignments like you would an instance variable or any
69
+ other method you might normally have access to:
70
+
71
+ = render bread_crumbs_for(category)
72
+ %h3#product_title= product.title
73
+ = render product
74
+ %h3 Associated Products
75
+ %ul
76
+ - associated_products.each do |associated_product|
77
+ %li= link_to(associated_product.title,product_path(associated_product))
78
+
79
+ Beware
80
+ ------
81
+
82
+ This is an exceptionally simple tool, which provides a solitary solution. It
83
+ must be used in conjunction with solid design approaches ("Program to an
84
+ interface, not an implementation.") and accepted best practices (e.g. Fat Model,
85
+ Skinny Controller). In itself, it won't heal a bad design. It is meant only to
86
+ be a tool to use in improving the overall design of a Ruby on Rails system and
87
+ moreover to provide a standard implementation for an emerging best practice.
88
+
89
+ Development
90
+ -----------
91
+
92
+ ### Running specs
93
+
94
+ `DecentExposure` has been developed with the philosophy that Ruby developers shouldn't
95
+ force their choice in RubyGems package managers on people consuming their code.
96
+ As a side effect of that, if you attempt to run the specs on this application,
97
+ you might get `no such file to load` errors. The short answer is that you can
98
+ `export RUBYOPT='rubygems'` and be on about your way (for the long answer, see
99
+ Ryan Tomayko's [excellent
100
+ treatise](http://tomayko.com/writings/require-rubygems-antipattern) on the
101
+ subject).
102
+
103
+ ### Documentation TODO
104
+
105
+ * walk-through of an actual implementation (using an existing, popular OSS Rails
106
+ app as an example refactor).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,21 @@
1
+ module DecentExposure
2
+ def expose(name, &block)
3
+ define_method name do
4
+ @_resources ||= {}
5
+ @_resources[name] ||= if block_given?
6
+ instance_eval(&block)
7
+ else
8
+ _class_for(name).find(params["#{name}_id"] || params['id'])
9
+ end
10
+ end
11
+ helper_method name
12
+ hide_action name
13
+ end
14
+
15
+ alias let expose
16
+
17
+ private
18
+ def _class_for(name)
19
+ name.to_s.classify.constantize
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ begin
2
+ require File.join(File.dirname(__FILE__), 'lib', 'decent_exposure') # From here
3
+ rescue LoadError
4
+ require 'decent_exposure' # From gem
5
+ end
6
+
7
+ ActionController::Base.extend(DecentExposure)
@@ -0,0 +1,2 @@
1
+ require 'mocha'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'decent_exposure')
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'helper')
2
+
3
+ class Quacker
4
+ extend DecentExposure
5
+ def self.helper_method(*args); end
6
+ def self.hide_action(*args); end
7
+ def self.find(*args); end
8
+ def memoizable(*args); args; end
9
+ def params; {'proxy_id' => 42}; end
10
+ expose(:proxy)
11
+ expose(:quack){ memoizable('quack!') }
12
+ end
13
+
14
+ describe DecentExposure do
15
+ context "classes extending DecentExposure" do
16
+ it "respond to :expose" do
17
+ Quacker.respond_to?(:expose).should be_true
18
+ end
19
+ end
20
+
21
+ context "#expose" do
22
+ let(:instance){ Quacker.new }
23
+
24
+ it "creates a method with the given name" do
25
+ Quacker.new.methods.map{|m| m.to_s}.should include('quack')
26
+ end
27
+
28
+ it "prevents the method from being a callable action" do
29
+ Quacker.expects(:hide_action).with(:blerg)
30
+ Quacker.class_eval do
31
+ expose(:blerg){ 'ehm' }
32
+ end
33
+ end
34
+
35
+ it "declares the method as a helper method" do
36
+ Quacker.stubs(:hide_action)
37
+ Quacker.expects(:helper_method).with(:blarg)
38
+ Quacker.class_eval do
39
+ expose(:blarg){ 'uhm' }
40
+ end
41
+ end
42
+
43
+ it "returns the value of the method" do
44
+ instance.quack.should == %w(quack!)
45
+ end
46
+
47
+ it "memoizes the value of the created method" do
48
+ instance.expects(:memoizable).once.returns('value')
49
+ instance.quack
50
+ instance.quack
51
+ end
52
+
53
+ context "when no block is given" do
54
+ before do
55
+ instance.stubs(:_class_for).returns(Quacker)
56
+ end
57
+ it "attempts to guess the class of the resource to expose" do
58
+ instance.expects(:_class_for).with(:proxy).returns(Quacker)
59
+ instance.proxy
60
+ end
61
+ it "calls find with {resource}_id on the resources class" do
62
+ Quacker.expects(:find).with(42)
63
+ instance.proxy
64
+ end
65
+ context "and there is no {resource}_id" do
66
+ before do
67
+ Quacker.class_eval do
68
+ def params; {'id' => 24}; end
69
+ end
70
+ end
71
+ it "calls find with params[:id] on the resources class" do
72
+ Quacker.expects(:find).with(24)
73
+ instance.proxy
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#_class_for' do
80
+ let(:name){ 'quacker' }
81
+ let(:classified_name){ 'Quacker' }
82
+ before do
83
+ name.stubs(:to_s => name, :classify => classified_name)
84
+ classified_name.stubs(:constantize => Quacker)
85
+ end
86
+ it 'retrieves a string representation of the class name' do
87
+ name.expects(:classify).returns(classified_name)
88
+ Quacker.send(:_class_for,name)
89
+ end
90
+ it 'returns the string representation of the name as a constant' do
91
+ classified_name.expects(:constantize)
92
+ Quacker.send(:_class_for,name)
93
+ end
94
+ end
95
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decent_exposure
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Caudill
8
+ - Jon Larkowski
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-01-31 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ type: :development
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 1.2.9
25
+ version:
26
+ description: "\n DecentExposure helps you program to an interface, rather than an implementation\n in your Rails controllers. The fact of the matter is that sharing state\n via instance variables in controllers promotes close coupling with views.\n DecentExposure gives you a declarative manner of exposing an interface to the\n state that controllers contain and thereby decreasing coupling and\n improving your testability and overall design.\n "
27
+ email: scaudill@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - README.md
34
+ files:
35
+ - COPYING
36
+ - README.md
37
+ - VERSION
38
+ - lib/decent_exposure.rb
39
+ - rails/init.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/voxdolo/decent_exposure
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A helper for creating declarative interfaces in controllers
68
+ test_files:
69
+ - spec/helper.rb
70
+ - spec/lib/decent_exposure_spec.rb