platanus 0.0.29 → 0.0.30
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/lib/platanus/stacked2.rb +174 -0
- data/lib/platanus/version.rb +1 -1
- data/platanus.gemspec +2 -0
- metadata +20 -3
@@ -0,0 +1,174 @@
|
|
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
|
+
# TODO
|
10
|
+
#
|
11
|
+
module StackedAttr2
|
12
|
+
|
13
|
+
class NotSupportedError < StandardError; end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend ClassMethods
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# Adds an stacked attribute to the model.
|
22
|
+
def has_stacked(_name, _options={})
|
23
|
+
|
24
|
+
# check option support
|
25
|
+
raise NotSupportedError.new('Only autosave mode is supported') if _options[:autosave] == false
|
26
|
+
raise NotSupportedError.new('has_many_through is not supported yet') if _options.has_key? :through
|
27
|
+
|
28
|
+
# prepare names
|
29
|
+
tname = _name.to_s
|
30
|
+
tname_single = tname.singularize
|
31
|
+
tname_class = _options.fetch(:class_name, tname_single.camelize)
|
32
|
+
|
33
|
+
# generate top_value property
|
34
|
+
top_value_prop = "top_#{tname_single}"
|
35
|
+
if _options.has_key? :top_value_key
|
36
|
+
belongs_to top_value_prop.to_sym, class_name: tname_class, foreign_key: _options.delete(:top_value_key)
|
37
|
+
elsif self.column_names.include? "#{top_value_prop}_id"
|
38
|
+
belongs_to top_value_prop.to_sym, class_name: tname_class
|
39
|
+
else
|
40
|
+
instance_var = "@_last_#{tname_single}".to_sym
|
41
|
+
send :define_method, top_value_prop do
|
42
|
+
# Storing the last stacked value will not prevent race conditions
|
43
|
+
# when simultaneous updates occur.
|
44
|
+
last = instance_variable_get instance_var
|
45
|
+
return last unless last.nil?
|
46
|
+
instance_variable_set(instance_var, self.send(tname).first)
|
47
|
+
end
|
48
|
+
send :define_method, "#{top_value_prop}=" do |_top|
|
49
|
+
instance_variable_set(instance_var, _top)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
send :private, "#{top_value_prop}="
|
53
|
+
|
54
|
+
prefix = if _options[:cache_prf].nil? then 'last_' else _options.delete(:cache_prf) end # TODO: deprecate
|
55
|
+
|
56
|
+
# generate virtual attributes
|
57
|
+
changes = {}
|
58
|
+
stacked_model = tname_class.constantize
|
59
|
+
stacked_model.accessible_attributes.each do |attr_name|
|
60
|
+
send :define_method, "#{attr_name}=" do |value| changes[attr_name] = value end
|
61
|
+
send :define_method, "#{attr_name}" do
|
62
|
+
return changes[attr_name] if changes.has_key? attr_name
|
63
|
+
return self.send(prefix + attr_name) if self.respond_to? prefix + attr_name # Return cached value if avaliable
|
64
|
+
top = self.send top_value_prop
|
65
|
+
return nil if top.nil?
|
66
|
+
return top.send attr_name
|
67
|
+
end
|
68
|
+
attr_accessible attr_name
|
69
|
+
end
|
70
|
+
|
71
|
+
# prepare cached attributes
|
72
|
+
to_cache = _options.delete(:cached)
|
73
|
+
unless to_cache.nil?
|
74
|
+
to_cache = to_cache.map do |cache_attr|
|
75
|
+
unless cache_attr.is_a? Hash
|
76
|
+
name = cache_attr.to_s
|
77
|
+
# attr_protected(prefix + name)
|
78
|
+
{ to: prefix + name, from: name }
|
79
|
+
else
|
80
|
+
# TODO: Test whether options are valid.
|
81
|
+
cache_attr
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# callbacks
|
87
|
+
on_stack = _options.delete(:on_stack)
|
88
|
+
|
89
|
+
# limits and ordering
|
90
|
+
# TODO: Support other kind of ordering, this would require to reevaluate top on every push
|
91
|
+
_options[:order] = 'created_at DESC, id DESC'
|
92
|
+
_options[:limit] = 10 if _options[:limit].nil?
|
93
|
+
|
94
|
+
# setup main association
|
95
|
+
has_many _name, _options
|
96
|
+
|
97
|
+
cache_step = ->(_ctx, _top, _top_is_new) {
|
98
|
+
# cache required fields
|
99
|
+
return if to_cache.nil?
|
100
|
+
to_cache.each do |cache_attr|
|
101
|
+
value = if cache_attr.has_key? :from
|
102
|
+
_top.nil? ? _top : _top.send(cache_attr[:from])
|
103
|
+
else
|
104
|
+
_ctx.send(cache_attr[:virtual], _top, _top_is_new)
|
105
|
+
end
|
106
|
+
_ctx.send(cache_attr[:to].to_s + '=', value)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
|
110
|
+
after_step = ->(_ctx, _top) {
|
111
|
+
# update top value property
|
112
|
+
_ctx.send("#{top_value_prop}=", _top)
|
113
|
+
|
114
|
+
# execute after callback
|
115
|
+
_ctx.send(on_stack, _top) unless on_stack.nil?
|
116
|
+
}
|
117
|
+
|
118
|
+
# before saving model, load changes from virtual attributes.
|
119
|
+
before_save do
|
120
|
+
if changes.count > 0
|
121
|
+
obj = stacked_model.new(changes)
|
122
|
+
changes = {} # since push sometimes saves, then we must prevent a stack overflow by unsetting changes here.
|
123
|
+
|
124
|
+
cache_step.call(self, obj, true)
|
125
|
+
self.save! if self.new_record? # make sure there is an id BEFORE pushing
|
126
|
+
raise ActiveRecord::RecordInvalid.new(obj) unless send(tname).send('<<', obj)
|
127
|
+
after_step.call(self, obj)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
send :define_method, "push_#{tname_single}!" do |obj|
|
132
|
+
self.class.transaction do
|
133
|
+
|
134
|
+
# cache, then save if new, then push and finally process state
|
135
|
+
cache_step.call(self, obj, true)
|
136
|
+
self.save! if self.new_record? # make sure there is an id BEFORE pushing
|
137
|
+
raise ActiveRecord::RecordInvalid.new(obj) unless send(tname).send('<<', obj)
|
138
|
+
after_step.call(self, obj)
|
139
|
+
|
140
|
+
self.save! if self.changed? # Must save again, no other way...
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
send :define_method, "push_#{tname_single}" do |obj|
|
145
|
+
begin
|
146
|
+
return send("push_#{tname_single}!", obj)
|
147
|
+
rescue ActiveRecord::RecordInvalid
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
send :define_method, "restore_#{tname}!" do
|
153
|
+
self.class.transaction do
|
154
|
+
|
155
|
+
# find current top, then restore stack state
|
156
|
+
top = self.send(_name).first
|
157
|
+
cache_step.call(self, top, false)
|
158
|
+
after_step.call(self, top)
|
159
|
+
|
160
|
+
self.save! if self.changed?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
send :define_method, "restore_#{tname}" do
|
165
|
+
begin
|
166
|
+
return send("restore_#{tname}!")
|
167
|
+
rescue ActiveRecord::RecordInvalid
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
data/lib/platanus/version.rb
CHANGED
data/platanus.gemspec
CHANGED
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.30
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-10-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multi_json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.3.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.3.2
|
14
30
|
description: Platan.us utility gem
|
15
31
|
email:
|
16
32
|
- ignacio@platan.us
|
@@ -36,6 +52,7 @@ files:
|
|
36
52
|
- lib/platanus/onetime.rb
|
37
53
|
- lib/platanus/serializers/json_sym.rb
|
38
54
|
- lib/platanus/stacked.rb
|
55
|
+
- lib/platanus/stacked2.rb
|
39
56
|
- lib/platanus/templates/prawn.rb
|
40
57
|
- lib/platanus/templates/spreadsheet.rb
|
41
58
|
- lib/platanus/traceable.rb
|