condi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +130 -0
- data/lib/condi.rb +22 -0
- data/test/test_condi.rb +91 -0
- metadata +50 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT LICENSE
|
2
|
+
from http://www.opensource.org/licenses/mit-license.php
|
3
|
+
|
4
|
+
Copyright (C) 2011 by Larry Kyrala
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
|
2
|
+
Condi
|
3
|
+
=====
|
4
|
+
|
5
|
+
Condi is a gem that you use with Rails to make it easier to cleanly implement conditional elements in a view.
|
6
|
+
|
7
|
+
Condi allows you to define predicates in the controller that are callable in the view without relying on unneeded instance variables or business logic in the views. Because the predicates are defined dynamically during a controller action, they are easy to find and easy to use without hopping around multiple files.
|
8
|
+
|
9
|
+
API Doc: {Condi}
|
10
|
+
|
11
|
+
Copyright (C) 2011 by Larry Kyrala. MIT Licensed.
|
12
|
+
|
13
|
+
Example
|
14
|
+
-------
|
15
|
+
|
16
|
+
For example, say you have a User who has various roles and a shopping Cart that contains items. Your StoreController loads the current User and Cart objects, and your store view displays the User and Cart information. But let's say that orders over a certain amount for new customers should show a "free ground shipping" option. With Condi you can do the following:
|
17
|
+
|
18
|
+
`app/controllers/store_controller.rb:`
|
19
|
+
|
20
|
+
class StoreController
|
21
|
+
include Condi
|
22
|
+
|
23
|
+
def checkout
|
24
|
+
user = User.find(user_id)
|
25
|
+
cart = Cart.find(cart_id)
|
26
|
+
predicate(:show_free_shipping?) { user.new_customer? && cart.amount > 100 }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
`app/views/store/checkout.html.erb:`
|
31
|
+
|
32
|
+
<% form_for @cart do |f| %>
|
33
|
+
<% if show_free_shipping? %>
|
34
|
+
<%= f.radio_button("cart", "shipping", "free_ground") %>
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
The Problem
|
39
|
+
-----------
|
40
|
+
|
41
|
+
Sometimes, pieces of your UI need to be enabled or disabled depending on certain criteria. Usually these criteria come from Models loaded during actions in your Controllers.
|
42
|
+
|
43
|
+
Here's a typical implementation of the above example without Condi:
|
44
|
+
|
45
|
+
class StoreController
|
46
|
+
def checkout
|
47
|
+
@user = User.find(user_id)
|
48
|
+
@cart = Cart.find(cart_id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
<% form_for @cart do |f| %>
|
53
|
+
<% if @user.new_customer? && @cart.amount > 100 %>
|
54
|
+
<%= f.radio_button("cart", "shipping", "free_ground") %>
|
55
|
+
<% end %>
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
Not the cleanest approach since business logic is in our views now. What's another alternative? Maybe we can stick a predicate for displaying ground shipping on the Cart model instead?
|
59
|
+
|
60
|
+
class Cart
|
61
|
+
def show_free_shipping?(new_customer)
|
62
|
+
new_customer && amount > 100
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
<% form_for @cart do |f| %>
|
67
|
+
<% if @cart.show_free_shipping?(@user.new_customer?) %>
|
68
|
+
<%= f.radio_button("cart", "shipping", "free_ground") %>
|
69
|
+
<% end %>
|
70
|
+
<% end %>
|
71
|
+
|
72
|
+
We haven't gained much except shuffling arguments around.
|
73
|
+
|
74
|
+
|
75
|
+
Or, we could put the predicate in a helper and remove the args:
|
76
|
+
|
77
|
+
class StoreHelper
|
78
|
+
def show_free_shipping?
|
79
|
+
@user.new_customer? && @cart.amount > 100
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
<% form_for @cart do |f| %>
|
84
|
+
<% if show_free_shipping? %>
|
85
|
+
<%= f.radio_button("cart", "shipping", "free_ground") %>
|
86
|
+
<% end %>
|
87
|
+
<% end %>
|
88
|
+
|
89
|
+
This is a little better, but now we have variables set up in the controller and business logic in the helper. It would be nicer if we could define the predicate in the controller where it is used, in the context of what the action has loaded (either into instance variables or locals). Also if we have a large data-driven UI, we may have many such conditional UI predicates. Managing them all can become quite complex.
|
90
|
+
|
91
|
+
|
92
|
+
Solution
|
93
|
+
--------
|
94
|
+
|
95
|
+
The way Condi solves this problem is to allow you to define predicates in the controller that are accessible in the view.
|
96
|
+
|
97
|
+
class StoreController
|
98
|
+
include Condi
|
99
|
+
|
100
|
+
def checkout
|
101
|
+
user = User.find(user_id)
|
102
|
+
cart = Cart.find(cart_id)
|
103
|
+
predicate(:show_free_shipping?) { user.new_customer? && cart.amount > 100 }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
The `predicate` call creates a closure around the state we've loaded in the controller and makes it available to the view later without cluttering up the helper namespace or forcing the view to contain business logic.
|
108
|
+
|
109
|
+
<% form_for @cart do |f| %>
|
110
|
+
<% if show_free_shipping? %>
|
111
|
+
<%= f.radio_button("cart", "shipping", "free_ground") %>
|
112
|
+
<% end %>
|
113
|
+
<% end %>
|
114
|
+
|
115
|
+
How does this work? Behind the scenes, `predicate` defines an instance method on the controller and then marks it as a `helper_method` which allows Rails to call the predicate from the view. Since the predicate is dynamically added, you don't have to worry about the controller instance containing any more predicates than you defined in that particular action, so it's easy to manage.
|
116
|
+
|
117
|
+
Advantages
|
118
|
+
----------
|
119
|
+
|
120
|
+
* Because predicates are closures, there is less of a chance that helpers and controllers and views get "mixed up" -- i.e. someone copies some code from one view to another but forgets to set the correct instance variables in the controller.
|
121
|
+
|
122
|
+
* Also, the Model shouldn't define such predicates, because they control behavior in the view, not to mention that predicates can orchestrate several Models together with business logic. The Controller is arguably a better place to define such predicates from an MVC perspective.
|
123
|
+
|
124
|
+
* Condi makes it simple to define predicates in the Controller and call them from the view without cluttering the helper namespace and creating a maze of unique names for every action. Condi is more flexible.
|
125
|
+
|
126
|
+
* Another important advantage of the predicate being defined as an instance method on the Controller during an action-view execution is that the predicate method will never exist on the Controller for subsequent actions -- hence the predicate can never be inadvertently called as an action itself.
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
|
data/lib/condi.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Include this module in an ActionController to define predicates within an action that
|
2
|
+
# can be used later in the related action view. For example:
|
3
|
+
# class StoreController
|
4
|
+
# include Condi
|
5
|
+
# ...
|
6
|
+
# end
|
7
|
+
module Condi
|
8
|
+
|
9
|
+
# define a predicate (instance method) on the controller which is callable from the related view.
|
10
|
+
# @example define a predicate that determines whether or not to show a "free shipping" option.
|
11
|
+
# predicate(:show_free_shipping?) { user.new_customer? && cart.amount > 100 }
|
12
|
+
# @param [Symbol] method_name name of the predicate method. (e.g. :show_action_button?)
|
13
|
+
# @param [Proc] block {} or do...end block that evaluates to true or false.
|
14
|
+
# @note You are not required to end your method name in a question mark, however it is conventional to do so.
|
15
|
+
# @see the full example in the README
|
16
|
+
def predicate(method_name, &block)
|
17
|
+
self.class.instance_eval do
|
18
|
+
define_method(method_name, &block)
|
19
|
+
helper_method(method_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/test/test_condi.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
require 'action_controller'
|
8
|
+
require 'condi'
|
9
|
+
|
10
|
+
#require 'ruby-debug'
|
11
|
+
|
12
|
+
|
13
|
+
class CondiTest < Test::Unit::TestCase
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@controller_class = Class.new(::ActionController::Base)
|
17
|
+
@controller_class.instance_eval do
|
18
|
+
include Condi
|
19
|
+
end
|
20
|
+
@controller_instance = @controller_class.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_included_properly
|
24
|
+
assert @controller_instance.respond_to?(:predicate)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_simple_predicate
|
28
|
+
@controller_instance.instance_eval do
|
29
|
+
predicate(:always_true?) { true }
|
30
|
+
end
|
31
|
+
|
32
|
+
assert @controller_instance.respond_to?(:always_true?)
|
33
|
+
assert @controller_instance.always_true? == true
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_instance_variable_predicate
|
37
|
+
@controller_instance.instance_eval do
|
38
|
+
@var = 5
|
39
|
+
predicate(:is_var_5?) { @var == 5 }
|
40
|
+
end
|
41
|
+
|
42
|
+
assert @controller_instance.respond_to?(:is_var_5?)
|
43
|
+
assert @controller_instance.is_var_5? == true
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_local_variable_predicate
|
47
|
+
@controller_instance.instance_eval do
|
48
|
+
var = "Mary"
|
49
|
+
predicate(:is_mary?) { (var =~ /Mary/) == 0 }
|
50
|
+
end
|
51
|
+
|
52
|
+
assert @controller_instance.respond_to?(:is_mary?)
|
53
|
+
assert @controller_instance.is_mary? == true
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_complex_multiline_predicate
|
57
|
+
@controller_instance.instance_eval do
|
58
|
+
# simulating loaded AR objects in a controller method.
|
59
|
+
@customer = Object.new
|
60
|
+
def @customer.new_customer?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
@cart = Object.new
|
64
|
+
def @cart.amount
|
65
|
+
105
|
66
|
+
end
|
67
|
+
predicate(:show_free_shipping?) do
|
68
|
+
@customer.new_customer? && @cart.amount > 100
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
assert @controller_instance.respond_to?(:show_free_shipping?)
|
73
|
+
assert @controller_instance.show_free_shipping? == true
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_exception_within_predicate
|
77
|
+
@controller_instance.instance_eval do
|
78
|
+
predicate(:blows_up?) do
|
79
|
+
raise "blew up!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
assert @controller_instance.respond_to?(:blows_up?)
|
84
|
+
assert_raise RuntimeError do
|
85
|
+
puts "ah ha!" if @controller_instance.blows_up?
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: condi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Larry Kyrala
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-14 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Conditional UI predicates for Rails
|
15
|
+
email: larry.kyrala@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/condi.rb
|
21
|
+
- README.md
|
22
|
+
- LICENSE
|
23
|
+
- test/test_condi.rb
|
24
|
+
homepage: http://github.com/coldnebo/condi
|
25
|
+
licenses: []
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements:
|
43
|
+
- rails
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 1.8.10
|
46
|
+
signing_key:
|
47
|
+
specification_version: 3
|
48
|
+
summary: Condi
|
49
|
+
test_files:
|
50
|
+
- test/test_condi.rb
|