platanus 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +32 -2
- data/lib/platanus/activable.rb +67 -0
- data/lib/platanus/canned.rb +222 -0
- data/lib/platanus/cmap.rb +80 -0
- data/lib/platanus/gcontroller.rb +26 -0
- data/lib/platanus/http_helpers.rb +61 -0
- data/lib/platanus/layered.rb +23 -0
- data/lib/platanus/onetime.rb +33 -0
- data/lib/platanus/stacked.rb +86 -0
- data/lib/platanus/templates/prawn.rb +26 -0
- data/lib/platanus/templates/spreadsheet.rb +24 -0
- data/lib/platanus/traceable.rb +74 -0
- data/lib/platanus/version.rb +1 -1
- data/lib/platanus.rb +1 -0
- data/platanus.gemspec +1 -1
- metadata +13 -1
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Platanus
|
2
2
|
|
3
|
-
|
3
|
+
Platan.us toolbelt.
|
4
|
+
|
5
|
+
This gem includes various ruby and rails utility classes we use across most of our rails proyects.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -16,7 +18,35 @@ Or install it yourself as:
|
|
16
18
|
|
17
19
|
$ gem install platanus
|
18
20
|
|
19
|
-
## Usage
|
21
|
+
## Tools & Usage
|
22
|
+
|
23
|
+
### Stacked
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
### Activable
|
28
|
+
|
29
|
+
TODO: Write usage instructions here
|
30
|
+
|
31
|
+
### Traceable
|
32
|
+
|
33
|
+
TODO: Write usage instructions here
|
34
|
+
|
35
|
+
### Canned
|
36
|
+
|
37
|
+
TODO: Write usage instructions here
|
38
|
+
|
39
|
+
### Gobal Controller
|
40
|
+
|
41
|
+
Requires explicit inclusion:
|
42
|
+
|
43
|
+
require 'platanus/gcontroller'
|
44
|
+
|
45
|
+
### Template: Spreadsheet
|
46
|
+
|
47
|
+
TODO: Write usage instructions here
|
48
|
+
|
49
|
+
### Template: prawn
|
20
50
|
|
21
51
|
TODO: Write usage instructions here
|
22
52
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# activable.rb : ActiveRecord Activable mod.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Platanus
|
6
|
+
|
7
|
+
# When included in a model definition, this module
|
8
|
+
# provides soft delete capabilities via the +remove+ method.
|
9
|
+
#
|
10
|
+
# This module also defines a +remove+ callback.
|
11
|
+
#
|
12
|
+
module Activable
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.define_callbacks :remove
|
16
|
+
base.attr_protected :removed_at
|
17
|
+
base.send(:default_scope, base.where(:removed_at => nil))
|
18
|
+
base.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Executes a mass remove, this wont call any callbacks!
|
23
|
+
def remove_all
|
24
|
+
# TODO: Find a way of doing mass updates and also call callbacks
|
25
|
+
# self.update_all(:removed_at => DateTime.now)
|
26
|
+
# For now, just call remove on each item
|
27
|
+
self.all.each { |item| item.remove! }
|
28
|
+
end
|
29
|
+
|
30
|
+
## Shorthand method for adding callbacks before item removal
|
31
|
+
def before_remove(_callback)
|
32
|
+
self.set_callback :remove, :before, _callback
|
33
|
+
end
|
34
|
+
|
35
|
+
## Shorthand method for adding callbacks after item removal
|
36
|
+
def after_remove(_callback)
|
37
|
+
self.set_callback :remove, :after, _callback
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if object hasnt been removed.
|
42
|
+
def is_active?
|
43
|
+
self.removed_at.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Deactivates a single record.
|
47
|
+
def remove!
|
48
|
+
self.transaction do
|
49
|
+
run_callbacks :remove do
|
50
|
+
|
51
|
+
# Retrieve dependant properties and remove them.
|
52
|
+
self.class.reflect_on_all_associations.select do |assoc|
|
53
|
+
if assoc.options[:dependent] == :destroy
|
54
|
+
collection = self.send(assoc.name)
|
55
|
+
collection.remove_all if collection.respond_to? :remove_all
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
self.removed_at = DateTime.now
|
60
|
+
self.save!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# canned.rb : User profiling and authorization.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Platanus
|
6
|
+
|
7
|
+
# User profiling and authorization module
|
8
|
+
module Canned
|
9
|
+
|
10
|
+
class Interrupt < Exception; end
|
11
|
+
class Error < StandardError; end
|
12
|
+
class AuthError < Error; end
|
13
|
+
class SetupError < Error; end
|
14
|
+
|
15
|
+
# Controller extension, include this in the the base application
|
16
|
+
# controller and use the barracks_setup method to define the profiles
|
17
|
+
# definition object and the user profile provider block.
|
18
|
+
module ControllerExt
|
19
|
+
|
20
|
+
attr_accessor :brk_tag
|
21
|
+
|
22
|
+
def self.included(klass)
|
23
|
+
class << klass
|
24
|
+
# Excluded actions are defined in per class basis
|
25
|
+
attr_accessor :brk_excluded
|
26
|
+
end
|
27
|
+
protected
|
28
|
+
# Definition and role provider are shared with subclasses
|
29
|
+
klass.cattr_accessor :brk_definition
|
30
|
+
klass.cattr_accessor :brk_provider
|
31
|
+
public
|
32
|
+
klass.extend ClassMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wraps a controller instance and provides the profile
|
36
|
+
# testing enviroment used by the role provider function.
|
37
|
+
class ActionWrapper
|
38
|
+
|
39
|
+
attr_reader :tag
|
40
|
+
|
41
|
+
# Loads the testing enviroment.
|
42
|
+
def initialize(_owner, _action, _actions_feats)
|
43
|
+
@owner = _owner
|
44
|
+
@action = _action
|
45
|
+
@feats = _actions_feats
|
46
|
+
@tag = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Test if a profile can execute the current action, raises a
|
50
|
+
# BarracksInterrupt exception if conditions are met.
|
51
|
+
def test(_profile, _user_feats, _tag=nil)
|
52
|
+
if @owner.class.brk_definition.can?(_profile, @action, @feats, _user_feats)
|
53
|
+
@tag = _tag
|
54
|
+
raise Interrupt
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Since we need to provide with all of controller functionality
|
59
|
+
# to provider, then proxy al failed method calls to it.
|
60
|
+
def method_missing(_method, *_args, &_block)
|
61
|
+
@owner.send(_method, *_args, &_block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Test if an action can be executed using the currently loaded roles.
|
66
|
+
def can?(_action, _action_feat)
|
67
|
+
wrapper = ActionWrapper.new(self,_action,_action_feat)
|
68
|
+
begin
|
69
|
+
provider = if brk_provider.is_a? Symbol then self.method(brk_provider) else brk_provider end
|
70
|
+
wrapper.instance_eval &provider
|
71
|
+
return false
|
72
|
+
rescue Interrupt
|
73
|
+
return (if wrapper.tag.nil? then true else wrapper.tag end)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module ClassMethods
|
78
|
+
|
79
|
+
# Setups the controller user profile definitions and
|
80
|
+
# profile provider block (or proc)
|
81
|
+
def canned_setup(_definition, _provider=nil, &pblock)
|
82
|
+
self.brk_definition = _definition
|
83
|
+
self.brk_provider = _provider || pblock
|
84
|
+
self.before_filter do
|
85
|
+
# Before filter is an instance_eval?
|
86
|
+
break if self.class.brk_excluded == :all
|
87
|
+
break if !self.class.brk_excluded.nil? and self.class.brk_excluded.include? params[:action].to_sym
|
88
|
+
tag = self.can?(params[:controller], params)
|
89
|
+
tag ||= self.can?(params[:controller] + '#' + params[:action], params)
|
90
|
+
raise AuthError unless tag
|
91
|
+
self.brk_tag = tag
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Removes protection for all controller actions.
|
96
|
+
def uncan_all()
|
97
|
+
self.brk_excluded = :all
|
98
|
+
end
|
99
|
+
|
100
|
+
# Removes protection for the especified controller actions.
|
101
|
+
def uncanned(*_excluded)
|
102
|
+
self.brk_excluded ||= []
|
103
|
+
self.brk_excluded.push(*_excluded)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Profile DSL
|
109
|
+
module ProfileManager
|
110
|
+
|
111
|
+
# Auxiliary class used by profile manager to store profiles.
|
112
|
+
class BProfile
|
113
|
+
|
114
|
+
# This is required for the copy constructor to work.
|
115
|
+
protected
|
116
|
+
attr_reader :rules
|
117
|
+
attr_reader :def_test
|
118
|
+
public
|
119
|
+
|
120
|
+
# The initializer takes another profile as rules base.
|
121
|
+
def initialize(_owner, _base, _def_test)
|
122
|
+
@owner = _owner
|
123
|
+
@rules = if _base.nil? then {} else _base.rules.clone end
|
124
|
+
@def_test = _def_test
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds a new ability.
|
128
|
+
def ability(*_args)
|
129
|
+
@rules[_args.first] = tests = {}
|
130
|
+
if _args.last.is_a? Hash
|
131
|
+
hook = _args.last.delete(@hook)
|
132
|
+
_args[1...-1].each { |sym| tests[sym] = @def_test }
|
133
|
+
tests.merge!(_args.last)
|
134
|
+
else
|
135
|
+
hook = nil
|
136
|
+
_args[1..-1].each { |sym| tests[sym] = @def_test }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Removes an action by its name.
|
141
|
+
def remove(_name)
|
142
|
+
@rules.delete(_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Test an action agaist this profile
|
146
|
+
def test(_action, _action_feats, _user_feats)
|
147
|
+
tests = @rules[_action]
|
148
|
+
return false if tests.nil?
|
149
|
+
tests.each do |sym, test|
|
150
|
+
|
151
|
+
# Analize test.
|
152
|
+
if test.is_a? Hash
|
153
|
+
test_name = test.fetch(:name,@def_test)
|
154
|
+
test_transform = test[:transform]
|
155
|
+
user_sym = test.fetch(:key,sym)
|
156
|
+
else
|
157
|
+
test_name = test
|
158
|
+
test_transform = nil
|
159
|
+
user_sym = sym
|
160
|
+
end
|
161
|
+
|
162
|
+
# Extract user and action features.
|
163
|
+
action_feat = _action_feats[sym]
|
164
|
+
raise SetupError if action_feat.nil?
|
165
|
+
user_feat = _user_feats[user_sym]
|
166
|
+
return false if user_feat.nil?
|
167
|
+
next if user_feat == :wildcard # Wildcard matches always
|
168
|
+
|
169
|
+
# Compare features.
|
170
|
+
action_feat = _owner.send(test_transform,action_feat) unless test_transform.nil?
|
171
|
+
case test_name
|
172
|
+
when :equals
|
173
|
+
return false unless user_feat == action_feat
|
174
|
+
when :equals_int
|
175
|
+
return false unless user_feat.to_i == action_feat.to_i
|
176
|
+
when :if_higher
|
177
|
+
return false unless user_feat > action_feat
|
178
|
+
when :if_lower
|
179
|
+
return false unless user_feat < action_feat
|
180
|
+
else
|
181
|
+
# TODO: Check that method exists first.
|
182
|
+
if @owner.method_defined? test_name
|
183
|
+
return false unless @owner.send(test_name,action_feat,user_value)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
return true
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.included(base)
|
193
|
+
class << base
|
194
|
+
# Add a profile property to extended class
|
195
|
+
attr_accessor :profiles
|
196
|
+
end
|
197
|
+
base.profiles = {}
|
198
|
+
base.extend ClassMethods
|
199
|
+
end
|
200
|
+
|
201
|
+
module ClassMethods
|
202
|
+
|
203
|
+
# Creates a new profile and passes it to the given block.
|
204
|
+
# This can optionally take an inherit parameter to use
|
205
|
+
# another profile as base for the new one.
|
206
|
+
def profile(_name, _options={})
|
207
|
+
inherit = _options.fetch(:inherits,nil)
|
208
|
+
def_test = _options.fetch(:default,:equals)
|
209
|
+
yield self.profiles[_name.to_s] = BProfile.new(self,self.profiles[inherit],def_test)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Test if a user (profile + user data) can execute a given
|
213
|
+
# action (action name + action data).
|
214
|
+
def can?(_profile, _action, _action_feat, _user_data)
|
215
|
+
profile = self.profiles[_profile.to_s]
|
216
|
+
return false if profile.nil?
|
217
|
+
return profile.test(_action,_action_feat,_user_data)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Platanus
|
2
|
+
|
3
|
+
module Cmap
|
4
|
+
|
5
|
+
class InvalidName < Exception; end
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.cattr_accessor :cmap
|
9
|
+
base.cmap = Hash.new { |hash, key| hash[key] = {} }
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
def cmap_register(_key, _value, _cat=nil)
|
16
|
+
if _cat.nil?; self.cmap[_key] = _value
|
17
|
+
else; self.cmap[_cat.to_s][_key] = _value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def cmap_convert(_key, _cat=nil)
|
22
|
+
if _cat.nil?; return self.cmap[_key]
|
23
|
+
else; return self.cmap[_cat.to_s][_key]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def cmap_convert_back(_value, _cat=nil)
|
28
|
+
if _cat.nil?; return self.cmap.key(_value)
|
29
|
+
else; return self.cmap[_cat.to_s].key(_value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def str_attr_accessor(_target, _options={})
|
34
|
+
str_attr_reader(_target,_options)
|
35
|
+
str_attr_writer(_target,_options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def str_attr_writer(_target, _options={})
|
39
|
+
_target = _target.to_s
|
40
|
+
_self = _options.fetch(:extend, self)
|
41
|
+
_cat = _options[:cat]
|
42
|
+
_cmap = self.cmap
|
43
|
+
|
44
|
+
_self.send(:define_method, _target + '_str=') do |value|
|
45
|
+
if _cat.nil?; self.send(_target + '=', _cmap.fetch(value))
|
46
|
+
else; self.send(_target + '=', _cmap[_cat.to_s].fetch(value)); end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def str_attr_reader(_target, _options={})
|
51
|
+
_target = _target.to_s
|
52
|
+
_self = _options.fetch(:extend, self)
|
53
|
+
_cat = _options[:cat]
|
54
|
+
_cmap = self.cmap
|
55
|
+
|
56
|
+
_self.send(:define_method, _target + '_str') do
|
57
|
+
if _cat.nil?; _cmap.key(self.send(_target))
|
58
|
+
else; _cmap[_cat.to_s].key(self.send(_target)); end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# private
|
63
|
+
# def cmap_converters(_cat_or_name=nil)
|
64
|
+
# # Define new class methods.
|
65
|
+
# klass = (class << self; self; end)
|
66
|
+
# klass.send(:define_method, _target + '_to_str') do |value|
|
67
|
+
# if _cat.nil?; _cmap.index(value)
|
68
|
+
# else; _cmap[_cat].index(value); end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# klass.send(:define_method, 'str_to_' + _target) do |value|
|
72
|
+
# if _cat.nil?; _cmap.fetch(value)
|
73
|
+
# else; _cmap[_cat].fetch(value); end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# gcontroller.rb : ActionController global access.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
class Platanus::NotInRequestError < Exception; end
|
6
|
+
|
7
|
+
# This makes the current request controller globally avaliable.
|
8
|
+
class ActionController::Base
|
9
|
+
around_filter :wrap_store_controller
|
10
|
+
|
11
|
+
def wrap_store_controller
|
12
|
+
# We could do this instead: http://coderrr.wordpress.com/2008/04/10/lets-stop-polluting-the-threadcurrent-hash/
|
13
|
+
Thread.current[:controller] = self
|
14
|
+
begin; yield
|
15
|
+
ensure; Thread.current[:controller] = nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets the current controller.
|
20
|
+
#
|
21
|
+
# * *Raises* :
|
22
|
+
# - +Platanus::NotInRequestError+ -> If current controller instance is not avaliable.
|
23
|
+
def self.current
|
24
|
+
Thread.current[:controller] || (raise Platanus::NotInRequestError, 'Current controller not loaded')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# http_helpers.rb : Various HTTP Related helpers
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Platanus
|
6
|
+
|
7
|
+
## HTTP STATUS EXCEPTIONS
|
8
|
+
|
9
|
+
# Allows to use an exception driven scheme to notify
|
10
|
+
# the request that a especial status should be send
|
11
|
+
# back to the client. Usefull when used with rescue_from.
|
12
|
+
class StatusError < StandardError
|
13
|
+
|
14
|
+
class << self
|
15
|
+
instance_variable_set(:@status, nil)
|
16
|
+
instance_variable_set(:@msg, nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.setup(_status, _msg)
|
20
|
+
@status = _status
|
21
|
+
@msg = _msg
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.status; @status; end
|
25
|
+
def self.message; @msg; end
|
26
|
+
|
27
|
+
def self.as_json(_options={})
|
28
|
+
{ 'status' => @status.to_s, 'msg' => @msg }
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(_msg=nil)
|
32
|
+
@msg = _msg
|
33
|
+
end
|
34
|
+
|
35
|
+
def status; self.class.status; end
|
36
|
+
def message; if @msg.nil? then self.class.message else @msg end; end
|
37
|
+
def as_json(_options={})
|
38
|
+
{ 'status' => status.to_s, 'msg' => message }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class StatusNotFound < StatusError
|
43
|
+
setup :not_found, 'Not found'
|
44
|
+
end
|
45
|
+
|
46
|
+
class StatusUnauthorized < StatusError
|
47
|
+
setup :unauthorized, 'Not authorized'
|
48
|
+
end
|
49
|
+
|
50
|
+
class StatusUnprocessable < StatusError
|
51
|
+
setup :unprocessable_entity, 'Invalid object'
|
52
|
+
end
|
53
|
+
|
54
|
+
class StatusBadRequest < StatusError
|
55
|
+
setup :bad_request, 'Invalid parameters'
|
56
|
+
end
|
57
|
+
|
58
|
+
class StatusConflict < StatusError
|
59
|
+
setup :conflict, 'Model conflict'
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Platanus
|
2
|
+
|
3
|
+
# Adds simple proxy capabilities to a class, allowing
|
4
|
+
# it instances to wrap other objects transparently
|
5
|
+
module Layered
|
6
|
+
attr_reader :lo_target
|
7
|
+
|
8
|
+
def initialize(_entity)
|
9
|
+
@lo_target = _entity
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to?(_what)
|
13
|
+
return true if super(_what)
|
14
|
+
return @lo_target.respond_to? _what
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(_method, *_args, &_block)
|
18
|
+
@lo_target.send(_method, *_args, &_block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# onetime.rb : One time setter.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
|
7
|
+
# Creates a one time writer method.
|
8
|
+
#
|
9
|
+
# A one time writer is undefined after the first time it is used.
|
10
|
+
#
|
11
|
+
# * *Args* :
|
12
|
+
# - +_name+ -> Attribute name.
|
13
|
+
#
|
14
|
+
def onetime_attr_writer(_name)
|
15
|
+
define_method _name.to_s + '=' do |_value|
|
16
|
+
instance_variable_set('@' + _name.to_s, _value)
|
17
|
+
# Unset method by modifying singleton class.
|
18
|
+
metaclass = (class << self; self; end)
|
19
|
+
metaclass.send(:undef_method,_name.to_s + '=')
|
20
|
+
_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a one time writer instead of a regular writer.
|
25
|
+
#
|
26
|
+
# * *Args* :
|
27
|
+
# - +_name+ -> Attribute name.
|
28
|
+
#
|
29
|
+
def onetime_attr_accessor(_name)
|
30
|
+
attr_reader(_name)
|
31
|
+
onetime_attr_writer(_name)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# stacked.rb : Stackable attributes for ActiveRecord
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Platanus
|
6
|
+
|
7
|
+
# Adds the has_stacked association to an ActiveRecord model.
|
8
|
+
#
|
9
|
+
module StackedAttr
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
# Adds an stacked attribute to the model.
|
18
|
+
def has_stacked(_name, _options={}, &block)
|
19
|
+
|
20
|
+
tname = _name.to_s
|
21
|
+
to_cache = _options.delete(:cached)
|
22
|
+
to_cache_prf = if _options[:cache_prf].nil? then 'last_' else _options.delete(:cache_prf) end
|
23
|
+
stack_key = if _options[:stack_key].nil? then 'created_at DESC, id DESC' else _options.delete(:stack_key) end
|
24
|
+
|
25
|
+
_options[:order] = stack_key
|
26
|
+
_options[:limit] = 10 if _options[:limit].nil?
|
27
|
+
|
28
|
+
# Protect cached attributes.
|
29
|
+
unless to_cache.nil?
|
30
|
+
to_cache = to_cache.map { |name| name.to_s }
|
31
|
+
to_cache.each { |name| attr_protected(to_cache_prf + name) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
has_many(_name,_options)
|
36
|
+
public
|
37
|
+
|
38
|
+
send :define_method, 'push_' + tname[0...-1] + '!' do |obj|
|
39
|
+
self.class.transaction do
|
40
|
+
|
41
|
+
# Execute before callbacks
|
42
|
+
self.send(_options[:before_push], obj) if _options.has_key? :before_push
|
43
|
+
block.call(self, obj) unless block.nil?
|
44
|
+
|
45
|
+
# Cache required fields
|
46
|
+
unless to_cache.nil?
|
47
|
+
to_cache.each { |name| send(to_cache_prf + name + '=', obj.send(name)) }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Save model and push attribute, cache last stacked attribute.
|
51
|
+
self.save! if _options[:autosave] and (self.new_record? or self.changed?)
|
52
|
+
raise ActiveRecord::RecordInvalid.new(obj) unless self.send(tname).send('<<',obj)
|
53
|
+
@_stacked_last = obj
|
54
|
+
|
55
|
+
# Execute after callback
|
56
|
+
self.send(_options[:after_push], obj) if _options.has_key? :after_push
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
send :define_method, 'push_' + tname[0...-1] do |obj|
|
61
|
+
begin
|
62
|
+
return self.send('push_' + tname[0...-1] + '!', obj)
|
63
|
+
rescue ActiveRecord::RecordInvalid
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
send :define_method, 'top_' + tname[0...-1] do
|
69
|
+
# Storing the last stacked value will not prevent race conditions
|
70
|
+
# when simultaneous updates occur.
|
71
|
+
return @_stacked_last unless @_stacked_last.nil?
|
72
|
+
self.send(_name).first
|
73
|
+
end
|
74
|
+
|
75
|
+
# Generate shorcut properties for cached attributes.
|
76
|
+
unless to_cache.nil?
|
77
|
+
to_cache.each do |name|
|
78
|
+
send :define_method, name.to_s do
|
79
|
+
self.send(to_cache_prf + name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# prawn.rb : Prawn - Rails Templates integration.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.ius+.
|
4
|
+
|
5
|
+
require 'prawn'
|
6
|
+
|
7
|
+
Mime::Type.register 'application/pdf', :pdf
|
8
|
+
|
9
|
+
module Platanus
|
10
|
+
|
11
|
+
# Template Handler, just exposes prawn to a template.
|
12
|
+
class PrawnBuilder
|
13
|
+
|
14
|
+
class_attribute :default_format
|
15
|
+
self.default_format = Mime::PDF
|
16
|
+
|
17
|
+
def self.call(template)
|
18
|
+
# Create a new pdf doc object using a block, populate the block using
|
19
|
+
# the template contents.
|
20
|
+
"pdf = Prawn::Document.new(:skip_page_creation => true);" + template.source + ";pdf.render;"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Register the template handler for the .prawn extension.
|
26
|
+
ActionView::Template.register_template_handler :prawn, Platanus::PrawnBuilder
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# spreadsheet.rb : Spreadsheet - Rails Templates integration.
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.ius+.
|
4
|
+
|
5
|
+
require 'spreadsheet'
|
6
|
+
|
7
|
+
Mime::Type.register 'application/vnd.ms-excel', :xls
|
8
|
+
|
9
|
+
module Platanus
|
10
|
+
|
11
|
+
# Template Handler, just exposes a spreadsheet's Workbook to a template.
|
12
|
+
class ExcelBuilder
|
13
|
+
|
14
|
+
class_attribute :default_format
|
15
|
+
self.default_format = Mime::XLS
|
16
|
+
|
17
|
+
def self.call(template)
|
18
|
+
"book = ::Spreadsheet::Workbook.new;" + template.source + ";io=StringIO.new('');book.write io;io.close;io.string;"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Register the template handler for the .spreadsht extension
|
24
|
+
ActionView::Template.register_template_handler :spreadsht, Platanus::ExcelBuilder
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# traceable.rb : Logged user action tracing
|
2
|
+
#
|
3
|
+
# Copyright April 2012, Ignacio Baixas +mailto:ignacio@platan.us+.
|
4
|
+
|
5
|
+
module Platanus
|
6
|
+
|
7
|
+
# When included in a model, this module will provide seamless C(R)UD operations
|
8
|
+
# tracing.
|
9
|
+
#
|
10
|
+
# This module operates under a couple of conventions:
|
11
|
+
# * When a new operation is detected it will look for the +user_id+ method for the current controller
|
12
|
+
# and assing it's value to the <action>_by attribute of the current model.
|
13
|
+
# * When a new operation is detected the controllers +trace+ method is called passing the action and
|
14
|
+
# the current model.
|
15
|
+
#
|
16
|
+
# This module will also trace +Activable.remove+ calls.
|
17
|
+
# Will only work if the gcontroller mod is active.
|
18
|
+
#
|
19
|
+
module Traceable
|
20
|
+
|
21
|
+
def self.included(base)
|
22
|
+
# Make sure gcontroller was loaded.
|
23
|
+
unless ActionController::Base.respond_to? :current
|
24
|
+
# TODO: better warning!
|
25
|
+
base.logger.warn 'gcontroller not loaded, tracing disabled'
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
base.around_create :__trace_create
|
30
|
+
base.around_update :__trace_update
|
31
|
+
base.around_destroy :__trace_destroy
|
32
|
+
# Activable support (remove event).
|
33
|
+
begin; base.set_callback :remove, :around, :__trace_remove; rescue; end
|
34
|
+
end
|
35
|
+
|
36
|
+
## CALLBACKS
|
37
|
+
|
38
|
+
def __trace_create # :nodoc:
|
39
|
+
controller = ActionController::Base.current
|
40
|
+
if controller.respond_to? :trace_user_id and self.respond_to? :created_by=
|
41
|
+
self.created_by = controller.trace_user_id
|
42
|
+
end
|
43
|
+
yield
|
44
|
+
controller.trace(:create, self) if controller.respond_to? :trace
|
45
|
+
end
|
46
|
+
|
47
|
+
def __trace_update # :nodoc:
|
48
|
+
controller = ActionController::Base.current
|
49
|
+
if controller.respond_to? :trace_user_id and self.respond_to? :updated_by=
|
50
|
+
self.updated_by = controller.trace_user_id
|
51
|
+
end
|
52
|
+
yield
|
53
|
+
controller.trace(:update, self) if controller.respond_to? :trace
|
54
|
+
end
|
55
|
+
|
56
|
+
def __trace_destroy # :nodoc:
|
57
|
+
controller = ActionController::Base.current
|
58
|
+
if controller.respond_to? :trace_user_id and self.respond_to? :destroyed_by=
|
59
|
+
self.destroyed_by = controller.trace_user_id
|
60
|
+
end
|
61
|
+
yield
|
62
|
+
controller.trace(:destroy, self) if controller.respond_to? :trace
|
63
|
+
end
|
64
|
+
|
65
|
+
def __trace_remove # :nodoc:
|
66
|
+
controller = ActionController::Base.current
|
67
|
+
if controller.respond_to? :trace_user_id and self.respond_to? :removed_by=
|
68
|
+
self.removed_by = controller.trace_user_id
|
69
|
+
end
|
70
|
+
yield
|
71
|
+
controller.trace(:remove, self) if controller.respond_to? :trace
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/platanus/version.rb
CHANGED
data/lib/platanus.rb
CHANGED
data/platanus.gemspec
CHANGED
@@ -12,6 +12,6 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
14
|
gem.name = "platanus"
|
15
|
-
gem.require_paths = ["lib"]
|
15
|
+
gem.require_paths = ["lib","lib/platanus"]
|
16
16
|
gem.version = Platanus::VERSION
|
17
17
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: platanus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -24,6 +24,17 @@ files:
|
|
24
24
|
- README.md
|
25
25
|
- Rakefile
|
26
26
|
- lib/platanus.rb
|
27
|
+
- lib/platanus/activable.rb
|
28
|
+
- lib/platanus/canned.rb
|
29
|
+
- lib/platanus/cmap.rb
|
30
|
+
- lib/platanus/gcontroller.rb
|
31
|
+
- lib/platanus/http_helpers.rb
|
32
|
+
- lib/platanus/layered.rb
|
33
|
+
- lib/platanus/onetime.rb
|
34
|
+
- lib/platanus/stacked.rb
|
35
|
+
- lib/platanus/templates/prawn.rb
|
36
|
+
- lib/platanus/templates/spreadsheet.rb
|
37
|
+
- lib/platanus/traceable.rb
|
27
38
|
- lib/platanus/version.rb
|
28
39
|
- platanus.gemspec
|
29
40
|
homepage: http://www.platan.us
|
@@ -32,6 +43,7 @@ post_install_message:
|
|
32
43
|
rdoc_options: []
|
33
44
|
require_paths:
|
34
45
|
- lib
|
46
|
+
- lib/platanus
|
35
47
|
required_ruby_version: !ruby/object:Gem::Requirement
|
36
48
|
none: false
|
37
49
|
requirements:
|