platanus 0.0.19 → 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,9 @@
4
4
 
5
5
  module Platanus
6
6
 
7
- # Adds the has_stacked association to an ActiveRecord model.
7
+ ## Adds the has_stacked association to an ActiveRecord model.
8
+ #
9
+ # TODO
8
10
  #
9
11
  module StackedAttr
10
12
 
@@ -17,61 +19,94 @@ module Platanus
17
19
  module ClassMethods
18
20
 
19
21
  # Adds an stacked attribute to the model.
20
- def has_stacked(_name, _options={}, &block)
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
21
27
 
28
+ # prepare names
22
29
  tname = _name.to_s
23
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
+ send :define_method, top_value_prop do
41
+ # Storing the last stacked value will not prevent race conditions
42
+ # when simultaneous updates occur.
43
+ return @_stacked_last unless @_stacked_last.nil?
44
+ @_stacked_last = self.send(tname).first
45
+ end
46
+ send :define_method, "#{top_value_prop}=" do |_top|
47
+ @_stacked_last = _top
48
+ end
49
+ end
50
+ send :private, "#{top_value_prop}="
24
51
 
52
+ # prepare cached attributes
25
53
  to_cache = _options.delete(:cached)
26
- to_cache_prf = if _options[:cache_prf].nil? then 'last_' else _options.delete(:cache_prf) end
54
+ to_cache_prf = if _options[:cache_prf].nil? then 'last_' else _options.delete(:cache_prf) end # TODO: deprecate
27
55
 
28
- # Retrieve callbacks
29
- before_push = _options.delete(:before_push)
30
- after_push = _options.delete(:after_push)
31
-
32
- raise NotSupportedError.new('Only autosave mode is supported') if _options[:autosave] == false
33
-
34
- _options[:order] = 'created_at DESC, id DESC'
35
- _options[:limit] = 10 if _options[:limit].nil?
36
-
37
- # Prepare cached attributes, generate read-only aliases without prefix.
38
56
  unless to_cache.nil?
39
- to_cache = to_cache.map do |name|
40
- name = name.to_s
41
- # attr_protected(to_cache_prf + name)
42
- send :define_method, name do self.send(to_cache_prf + name) end
43
- name
57
+ to_cache = to_cache.map do |cache_attr|
58
+ unless cache_attr.is_a? Hash
59
+ name = cache_attr.to_s
60
+ # attr_protected(to_cache_prf + name)
61
+ send :define_method, name do self.send(to_cache_prf + name) end # generate read-only aliases without prefix. TODO: deprecate
62
+ { to: to_cache_prf + name, from: name }
63
+ else
64
+ # TODO: Test whether options are valid.
65
+ cache_attr
66
+ end
44
67
  end
45
68
  end
46
69
 
47
- private
48
- has_many _name, _options
49
- public
70
+ # callbacks
71
+ on_stack = _options.delete(:on_stack)
50
72
 
51
- send :define_method, "push_#{tname_single}!" do |obj|
52
- self.class.transaction do
73
+ # limits and ordering
74
+ # TODO: Support other kind of ordering, this would require to reevaluate top on every push
75
+ _options[:order] = 'created_at DESC, id DESC'
76
+ _options[:limit] = 10 if _options[:limit].nil?
53
77
 
54
- # execute before callbacks
55
- self.send(before_push, obj) unless before_push.nil?
56
- block.call(self, obj) unless block.nil?
78
+ # setup main association
79
+ has_many _name, _options
57
80
 
58
- # cache required fields
59
- unless to_cache.nil?
60
- to_cache.each { |name| send(to_cache_prf + name + '=', obj.send(name)) }
81
+ cache_step = ->(_ctx, _top) {
82
+ # cache required fields
83
+ return if to_cache.nil?
84
+ to_cache.each do |cache_attr|
85
+ value = if cache_attr.has_key? :from
86
+ _top.nil? ? _top : _top.send(cache_attr[:from])
87
+ else
88
+ _ctx.send(cache_attr[:virtual], _top)
61
89
  end
90
+ _ctx.send(cache_attr[:to].to_s + '=', value)
91
+ end
92
+ }
62
93
 
63
- # push attribute, this will save the model if new.
64
- raise ActiveRecord::RecordInvalid.new(obj) unless self.send(tname).send('<<',obj)
94
+ after_step = ->(_ctx, _top) {
95
+ # update top value property
96
+ _ctx.send("#{top_value_prop}=", _top)
65
97
 
66
- # update inverse association if posible
67
- if self.attributes.has_key? "top_#{tname_single}_id"
68
- self["top_#{tname_single}_id"] = obj.id
69
- else
70
- @_stacked_last = obj
71
- end
98
+ # execute after callback
99
+ _ctx.send(on_stack, _top) unless on_stack.nil?
100
+ }
72
101
 
73
- # execute after callback
74
- self.send(after_push, obj) unless after_push.nil?
102
+ send :define_method, "push_#{tname_single}!" do |obj|
103
+ self.class.transaction do
104
+
105
+ # cache, then save if new, then push and finally process state
106
+ cache_step.call(self, obj)
107
+ self.save! if self.new_record? # make sure there is an id BEFORE pushing
108
+ raise ActiveRecord::RecordInvalid.new(obj) unless send(tname).send('<<',obj)
109
+ after_step.call(self, obj)
75
110
 
76
111
  self.save! if self.changed? # Must save again, no other way...
77
112
  end
@@ -79,18 +114,29 @@ module Platanus
79
114
 
80
115
  send :define_method, "push_#{tname_single}" do |obj|
81
116
  begin
82
- return self.send("push_#{tname_single}!", obj)
117
+ return send("push_#{tname_single}!", obj)
83
118
  rescue ActiveRecord::RecordInvalid
84
119
  return false
85
120
  end
86
121
  end
87
122
 
88
- unless self.column_names.include? "top_#{tname_single}_id"
89
- send :define_method, "top_#{tname_single}" do
90
- # Storing the last stacked value will not prevent race conditions
91
- # when simultaneous updates occur.
92
- return @_stacked_last unless @_stacked_last.nil?
93
- @_stacked_last = self.send(_name).first
123
+ send :define_method, "restore_#{tname}!" do
124
+ self.class.transaction do
125
+
126
+ # find current top, then restore stack state
127
+ top = self.send(_name).first
128
+ cache_step.call(self, top)
129
+ after_step.call(self, top)
130
+
131
+ self.save! if self.changed?
132
+ end
133
+ end
134
+
135
+ send :define_method, "restore_#{tname}" do
136
+ begin
137
+ return send("restore_#{tname}!")
138
+ rescue ActiveRecord::RecordInvalid
139
+ return false
94
140
  end
95
141
  end
96
142
  end
@@ -1,3 +1,3 @@
1
1
  module Platanus
2
- VERSION = "0.0.19" # 0.1 will come with tests!
2
+ VERSION = "0.0.21" # 0.1 will come with tests!
3
3
  end
data/lib/platanus.rb CHANGED
@@ -5,7 +5,6 @@ require 'platanus/stacked'
5
5
  require 'platanus/activable'
6
6
  require 'platanus/traceable'
7
7
  require 'platanus/layered'
8
- require 'platanus/cmap'
9
8
  require 'platanus/enum'
10
9
  require 'platanus/http_helpers'
11
10
 
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.19
4
+ version: 0.0.21
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-03 00:00:00.000000000 Z
12
+ date: 2012-10-08 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Platan.us utility gem
15
15
  email:
@@ -28,7 +28,6 @@ files:
28
28
  - lib/platanus/activable.rb
29
29
  - lib/platanus/api_base.rb
30
30
  - lib/platanus/canned.rb
31
- - lib/platanus/cmap.rb
32
31
  - lib/platanus/enum.rb
33
32
  - lib/platanus/gcontroller.rb
34
33
  - lib/platanus/http_helpers.rb
data/lib/platanus/cmap.rb DELETED
@@ -1,80 +0,0 @@
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
-