platanus 0.0.29 → 0.0.30

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Platanus
2
- VERSION = "0.0.29" # 0.1 will come with tests!
2
+ VERSION = "0.0.30" # 0.1 will come with tests!
3
3
  end
data/platanus.gemspec CHANGED
@@ -14,4 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "platanus"
15
15
  gem.require_paths = ["lib","lib/platanus"]
16
16
  gem.version = Platanus::VERSION
17
+
18
+ gem.add_runtime_dependency "multi_json", [">= 1.3.2"]
17
19
  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.29
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-29 00:00:00.000000000 Z
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