acts_as_itemized 0.1.0

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.
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
+