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.
- data/lib/acts_as_itemized.rb +277 -0
- 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
|
+
|