platanus 0.0.1 → 0.0.2
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/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:
|