acts_as_itemized 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/acts_as_itemized.rb +277 -0
  2. metadata +67 -0
@@ -0,0 +1,277 @@
1
+ module ActsAsItemized
2
+
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_itemized
9
+ send :include, InstanceMethods
10
+ send :extend, SingletonMethods
11
+
12
+ after_save :commit_item_changes
13
+
14
+ has_many :itemized_items, :order => "position" do
15
+ def where_type(conditions)
16
+ conditions = (conditions.is_a? Array) ? conditions.collect(&:to_s) : conditions.to_s
17
+ scoped(:conditions => {:item_type => conditions } )
18
+ end
19
+ def where_position(conditions)
20
+ conditions.collect!(&:to_i) if conditions.is_a? Array
21
+ scoped(:conditions => {:position => conditions})
22
+ end
23
+ def scores
24
+ all.collect(&:score)
25
+ end
26
+ def contents
27
+ all.collect(&:content)
28
+ end
29
+ def notes
30
+ all.collect(&:note)
31
+ end
32
+ def to_h
33
+ all.inject({}) do |result, item|
34
+ key = item.item_type
35
+ result[key] = ( result[key] || {} ).merge(item.to_h)
36
+ result
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ module SingletonMethods
47
+ def items(type = false)
48
+ sql = (sql || "") + "itemized_items.item_type = ? AND " if type
49
+ sql = (sql || "") + "itemized_items.parent_id IN (?) "
50
+ conditions = [sql]
51
+ conditions << type.to_s if type
52
+ conditions << all
53
+ ItemizedItem.scoped(:conditions => conditions)
54
+ end
55
+ end
56
+
57
+ module InstanceMethods
58
+
59
+ def items
60
+ itemized_items
61
+ end
62
+
63
+ def item_options
64
+ @item_options ||= {}
65
+ end
66
+ def item_options_with_sorting
67
+ item_options.sort_by{|k,io| io[:position] || 0}
68
+ end
69
+
70
+ # Load items into @attributes from self.itemized_items
71
+ def eager_load_items
72
+ item_options.each do |type_many, options|
73
+ type_one = type_many.to_s.singularize
74
+ item_options[type_many][:count].times do |id|
75
+ position = id + 1
76
+ options[:columns].each do |column|
77
+ self.send("#{type_one}_#{column}_#{position}")
78
+ end
79
+ end
80
+ end
81
+ self.attributes
82
+ end
83
+
84
+ # ITEM CHANGES
85
+
86
+ def item_changes
87
+ @item_changes ||= {}
88
+ end
89
+ def previous_item_changes
90
+ @previous_item_changes ||= {}
91
+ end
92
+ def items_changed?
93
+ !item_changes.blank?
94
+ end
95
+ def previous_items_changed?
96
+ !previous_item_changes.blank?
97
+ end
98
+
99
+ def reload
100
+ item_changes.each{|key,values| @attributes.delete(key) }
101
+ super
102
+ end
103
+
104
+
105
+ protected
106
+
107
+ # ITEM CHANGES
108
+ def add_items(*args)
109
+ args.each do |arg|
110
+ next if item_options.has_key?(arg.is_a?(Hash) ? arg.keys.first : arg)
111
+ options = item_options_with_defaults(arg)
112
+ @item_options.merge!(options)
113
+ create_item_accessors(options)
114
+ end
115
+ end
116
+
117
+ def item_options_with_defaults(arg)
118
+ key = arg.is_a?(Hash) ? arg.keys.first : arg.to_sym
119
+ options = arg.is_a?(Hash) ? arg[key] : {}
120
+ options = {
121
+ :count => 1,
122
+ :columns => [:content],
123
+ :position => item_position_with_auto_increment,
124
+ :name => key.to_s.titleize,
125
+ :tabindex => [],
126
+ }.merge(options)
127
+ options[:tabindex] = tabindex_with_auto_increment(options[:count], options[:tabindex][0], options[:tabindex][1], options[:tabindex][2])
128
+ options[:tabindex_score] = tabindex_with_auto_increment(options[:count], options[:tabindex_score][0], options[:tabindex_score][1], options[:tabindex_score][2]) if options[:tabindex_score]
129
+ return {key => options}
130
+ end
131
+
132
+ def set_item_change(changes)
133
+ changes.each do |key, value|
134
+ next if attributes[key] == value
135
+ @item_changes[key] = [attributes[key]] unless item_changes.has_key?(key)
136
+ @item_changes[key][1] = value
137
+ end
138
+ end
139
+ def commit_item_changes
140
+ item_changes.each do |key, values|
141
+ update_or_create_item(key, values[1])
142
+ end
143
+ rotate_item_changes
144
+ end
145
+ def rotate_item_changes
146
+ @previous_item_changes = item_changes
147
+ @item_changes = {}
148
+ end
149
+
150
+
151
+ private
152
+
153
+ def item_position
154
+ @item_position ||= 0
155
+ end
156
+ def item_position_with_auto_increment
157
+ @item_position = item_position + 1
158
+ end
159
+
160
+ def tabindex
161
+ @tabindex ||= 1
162
+ end
163
+ def tabindex_with_auto_increment(*args)
164
+ # args
165
+ times = (args.first || 1)
166
+ increase_by = (args.second || 1)
167
+ offset = (args.third || 1) - 1
168
+ alternation = (args.fourth || false)
169
+ alternate = false
170
+ # tabindex
171
+ ticker = tabindex
172
+ total = (tabindex) + (times * increase_by)
173
+ @tabindex = tabindex + times
174
+ # iterate
175
+ result = []
176
+ if alternation
177
+ while ticker < total do
178
+ result << (ticker + offset) + (alternate ? (times / 2) : (((total - ticker) / 2) - (times / 2)) )
179
+ alternate = !alternate
180
+ ticker += increase_by
181
+ end
182
+ else
183
+ while ticker < total do
184
+ result << (ticker + offset) - (times * offset)
185
+ ticker += increase_by
186
+ end
187
+ end
188
+ result
189
+ end
190
+
191
+
192
+ # ITEM CRUD
193
+
194
+ def create_item_accessors(_item_options)
195
+ _item_options.each do |type_many, options|
196
+ # singular item
197
+ type_one = type_many.to_s.singularize
198
+ # total
199
+ count = _item_options[type_many][:count]
200
+ # access all of type
201
+ class_eval <<-END, __FILE__, (__LINE__+1)
202
+ def #{type_many}
203
+ @#{type_many} ||= #{count}.times.inject([]){|r,position| r << self.items.detect{|i| i.item_type == '#{type_many}' && i.position == position }; r}
204
+ end
205
+ END
206
+ # for each item type this many times
207
+ count.times do |id|
208
+ position = id + 1
209
+ # individual accessors
210
+ options[:columns].each do |column|
211
+ # create getter, setter, and config
212
+ next if self.respond_to? "#{type_one}_#{column}_#{position}"
213
+ class_eval <<-END, __FILE__, (__LINE__+1)
214
+ def #{type_one}_#{column}_#{position}
215
+ get_item_value("#{type_one}_#{column}_#{position}")
216
+ end
217
+ def #{type_one}_#{column}_#{position}=(value)
218
+ set_item_value("#{type_one}_#{column}_#{position}", value)
219
+ end
220
+ def #{type_one}_#{column}_#{position}_options
221
+ @#{type_one}_#{column}_#{position}_options ||= item_options[:#{type_many}].merge({ :item_type => '#{type_many}', :column => '#{column}', :position => #{position} })
222
+ end
223
+ END
224
+ end
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+
231
+ # ITEM GETTER & SETTER
232
+
233
+ def get_item_value(key)
234
+ @attributes[key] = get_item_record(key) unless @attributes.has_key?(key)
235
+ @attributes[key]
236
+ end
237
+
238
+ def set_item_value(key, value)
239
+ options = self.send("#{key}_options")
240
+ if options[:through] && self.respond_to?(options[:through]) && !self.send(options[:through]).blank?
241
+ self.send(options[:through]).send("#{key}=", value)
242
+ else
243
+ set_item_change({ key => value })
244
+ end
245
+ @attributes[key] = value
246
+ end
247
+
248
+
249
+ def get_item_record(key)
250
+ options = self.send("#{key}_options")
251
+ # get through delegate, if defined
252
+ return self.send(options[:through]).send(key) if options[:through] && self.respond_to?(options[:through]) && !self.send(options[:through]).blank?
253
+ # get through itemized_item record
254
+ return nil unless item = items.detect{|i| i.item_type == options[:item_type] && i.position == options[:position] }
255
+ item.send options[:column]
256
+ end
257
+
258
+ def update_or_create_item(key, value)
259
+ options = self.send("#{key}_options")
260
+ # find or create
261
+ item = items.detect{|i| i.item_type == options[:item_type] && i.position == options[:position]} || create_item(options[:item_type], options[:position])
262
+ # update
263
+ item.send("#{options[:column]}=", value)
264
+ item.save
265
+ end
266
+
267
+ def create_item(item_type, position, conditions = {})
268
+ record = conditions.merge({:item_type => item_type.to_s, :position => position})
269
+ items.create(record)
270
+ end
271
+
272
+
273
+ end
274
+
275
+ end
276
+
277
+ ActiveRecord::Base.class_eval { include ActsAsItemized }
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_itemized
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Blake Hilscher
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-26 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Create virtual mutable columns on activerecord models.
23
+ email: blake@hilscher.ca
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/acts_as_itemized.rb
32
+ has_rdoc: true
33
+ homepage: http://rubygems.org/gems/acts_as_itemized
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ hash: 3
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.6.2
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Gem version of acts_as_itemized library.
66
+ test_files: []
67
+