benhoskings-hammock 0.2.4

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 (42) hide show
  1. data/LICENSE +24 -0
  2. data/Manifest.txt +42 -0
  3. data/README.rdoc +105 -0
  4. data/Rakefile +27 -0
  5. data/lib/hammock.rb +25 -0
  6. data/lib/hammock/ajaxinate.rb +152 -0
  7. data/lib/hammock/callbacks.rb +107 -0
  8. data/lib/hammock/canned_scopes.rb +121 -0
  9. data/lib/hammock/constants.rb +7 -0
  10. data/lib/hammock/controller_attributes.rb +66 -0
  11. data/lib/hammock/export_scope.rb +74 -0
  12. data/lib/hammock/hamlink_to.rb +47 -0
  13. data/lib/hammock/javascript_buffer.rb +63 -0
  14. data/lib/hammock/logging.rb +98 -0
  15. data/lib/hammock/model_attributes.rb +38 -0
  16. data/lib/hammock/model_logging.rb +30 -0
  17. data/lib/hammock/monkey_patches/action_pack.rb +32 -0
  18. data/lib/hammock/monkey_patches/active_record.rb +227 -0
  19. data/lib/hammock/monkey_patches/array.rb +73 -0
  20. data/lib/hammock/monkey_patches/hash.rb +49 -0
  21. data/lib/hammock/monkey_patches/logger.rb +28 -0
  22. data/lib/hammock/monkey_patches/module.rb +27 -0
  23. data/lib/hammock/monkey_patches/numeric.rb +25 -0
  24. data/lib/hammock/monkey_patches/object.rb +61 -0
  25. data/lib/hammock/monkey_patches/route_set.rb +200 -0
  26. data/lib/hammock/monkey_patches/string.rb +197 -0
  27. data/lib/hammock/overrides.rb +32 -0
  28. data/lib/hammock/resource_mapping_hooks.rb +28 -0
  29. data/lib/hammock/resource_retrieval.rb +115 -0
  30. data/lib/hammock/restful_actions.rb +170 -0
  31. data/lib/hammock/restful_rendering.rb +114 -0
  32. data/lib/hammock/restful_support.rb +167 -0
  33. data/lib/hammock/route_drawing_hooks.rb +22 -0
  34. data/lib/hammock/route_for.rb +58 -0
  35. data/lib/hammock/scope.rb +120 -0
  36. data/lib/hammock/suggest.rb +36 -0
  37. data/lib/hammock/utils.rb +42 -0
  38. data/misc/scaffold.txt +83 -0
  39. data/misc/template.rb +17 -0
  40. data/tasks/hammock_tasks.rake +5 -0
  41. data/test/hammock_test.rb +8 -0
  42. metadata +129 -0
@@ -0,0 +1,38 @@
1
+ module Hammock
2
+ module ModelAttributes
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def has_defaults attrs
13
+ write_inheritable_attribute "default_attributes", (default_attributes || {}).merge(attrs)
14
+ end
15
+ def attr_accessible_on_create *attributes
16
+ write_inheritable_attribute "attr_accessible_on_create", Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_create || [])
17
+ end
18
+ def attr_accessible_on_update *attributes
19
+ write_inheritable_attribute "attr_accessible_on_update", Set.new(attributes.map(&:to_s)) + (accessible_attributes_on_update || [])
20
+ end
21
+
22
+ def default_attributes
23
+ read_inheritable_attribute("default_attributes") || {}
24
+ end
25
+ def accessible_attributes_on_create
26
+ read_inheritable_attribute "attr_accessible_on_create"
27
+ end
28
+ def accessible_attributes_on_update
29
+ read_inheritable_attribute "attr_accessible_on_update"
30
+ end
31
+
32
+ end
33
+
34
+ module InstanceMethods
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ module Hammock
2
+ module ModelLogging
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ include Hammock::Utils::Methods
12
+ include Hammock::Logging::Methods
13
+ end
14
+
15
+ module InstanceMethods
16
+ include Hammock::Utils::Methods
17
+ include Hammock::Logging::Methods
18
+
19
+ def log_with_model *args
20
+ opts = args.extract_options!
21
+
22
+ message = "#{self.class}<#{self.id}>#{(' | ' + args.shift) if args.first.is_a?(String)}"
23
+
24
+ log_without_model *args.unshift(message).push(opts.merge(:skip => (opts[:skip] || 0) + 1))
25
+ end
26
+ alias_method_chain :log, :model
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ module Hammock
2
+ module ActionControllerPatches
3
+ MixInto = ActionController::Rescue
4
+
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+
9
+ # base.class_eval {
10
+ # alias_method_chain :clean_backtrace, :truncation
11
+ # }
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ def clean_backtrace_with_truncation exception
23
+ if backtrace = clean_backtrace_without_truncation(exception)
24
+ backtrace.take_while {|line|
25
+ line['perform_action_without_filters'].nil?
26
+ }.push("... and so on")
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,227 @@
1
+ module Hammock
2
+ module ActiveRecordPatches
3
+ MixInto = ActiveRecord::Base
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods # TODO maybe include in the metaclass instead of extending the class?
8
+
9
+ %w[before_undestroy after_undestroy].each {|callback_name|
10
+ MixInto.define_callbacks callback_name
11
+ # base.send :define_method, callback_name, lambda { }
12
+ }
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def new_with attributes
18
+ default_attributes.merge(attributes).inject(new) {|record,(k,v)|
19
+ record.send "#{k}=", v
20
+ record
21
+ }
22
+ end
23
+
24
+ def sorter
25
+ # TODO updated_at DESC
26
+ proc {|record| record.id }
27
+ end
28
+
29
+ def resource
30
+ base_class
31
+ end
32
+
33
+ def resource_sym
34
+ resource_name.to_sym
35
+ end
36
+
37
+ def resource_name
38
+ # TODO almost certainly a better way to do this
39
+ base_class.to_s.pluralize.underscore
40
+ end
41
+
42
+ def base_model
43
+ base_class.to_s.underscore
44
+ end
45
+
46
+ def record?; false end
47
+ def resource?; true end
48
+
49
+ def update_statement set_clause, where_clause
50
+ statement = "UPDATE #{table_name} SET #{set_clause} WHERE #{send :sanitize_sql_array, where_clause}"
51
+ connection.update statement
52
+ end
53
+
54
+ def reset_cached_column_info
55
+ reset_column_information
56
+ subclasses.each &:reset_cached_column_info
57
+ end
58
+
59
+ def find_or_new_with(find_attributes, create_attributes = {})
60
+ finder = respond_to?(:find_with_deleted) ? :find_with_deleted : :find
61
+
62
+ if record = send(finder, :first, :conditions => find_attributes.discard(:deleted_at))
63
+ # Found the record, so we can return it, if:
64
+ # (a) the record can't have a stored deletion state,
65
+ # (b) it can, but it's not actually deleted,
66
+ # (c) it is deleted, but we want to find one that's deleted, or
67
+ # (d) we don't want a deleted record, and undestruction succeeds.
68
+ if (finder != :find_with_deleted) || !record.deleted? || create_attributes[:deleted_at] || record.undestroy
69
+ record
70
+ end
71
+ else
72
+ creating_class = if create_attributes[:type].is_a?(ActiveRecord::Base)
73
+ create_attributes.delete(:type)
74
+ else
75
+ self
76
+ end
77
+ creating_class.new_with create_attributes.merge(find_attributes)
78
+ end
79
+ end
80
+
81
+ def find_or_create_with(find_attributes, create_attributes = {}, adjust_attributes = false)
82
+ if record = find_or_new_with(find_attributes, create_attributes)
83
+ log "Create failed. #{record.errors.inspect}", :skip => 1 if record.new_record? && !record.save
84
+ log "Adjust failed. #{record.errors.inspect}", :skip => 1 if adjust_attributes && !record.adjust(create_attributes)
85
+ record
86
+ end
87
+ end
88
+
89
+ # def find_or_create_with! find_attributes, create_attributes = {}, adjust_attributes = false)
90
+ # record = find_or_new_with find_attributes, create_attributes, adjust_attributes
91
+ # record.valid? ? record : raise("Save failed. #{record.errors.inspect}")
92
+ # end
93
+
94
+ end
95
+
96
+ module InstanceMethods
97
+
98
+ def concise_inspect
99
+ "#{self.class}<#{self.id || 'new'}>"
100
+ end
101
+
102
+ def resource
103
+ self.class.resource
104
+ end
105
+
106
+ def resource_sym
107
+ self.class.resource_sym
108
+ end
109
+
110
+ def resource_name
111
+ self.class.resource_name
112
+ end
113
+
114
+ def record?; true end
115
+ def resource?; false end
116
+
117
+ def id_str
118
+ if new_record?
119
+ "new_#{base_model}"
120
+ else
121
+ "#{base_model}_#{id}"
122
+ end
123
+ end
124
+
125
+ def id_or_description
126
+ new_record? ? new_record_description : id
127
+ end
128
+
129
+ def new_record_description
130
+ attributes.map {|k,v| "#{k}-#{(v.to_s || '')[0..10]}" }.join("_")
131
+ end
132
+
133
+ def base_model
134
+ self.class.base_model
135
+ end
136
+
137
+ def new_or_deleted_before_save?
138
+ @new_or_deleted_before_save
139
+ end
140
+ def set_new_or_deleted_before_save
141
+ @new_or_deleted_before_save = new_record? || send_if_respond_to(:deleted?)
142
+ end
143
+
144
+ def undestroy
145
+ unless new_record?
146
+ if frozen?
147
+ self.class.find_with_deleted(self.id).undestroy # Re-fetch ourselves and undestroy the thawed copy
148
+ else
149
+ # We can undestroy
150
+ return false if callback(:before_undestroy) == false
151
+ result = self.class.update_all ['deleted_at = ?', (self.deleted_at = nil)], ['id = ?', self.id]
152
+ callback(:after_undestroy)
153
+ self if result != false
154
+ end
155
+ end
156
+ end
157
+
158
+ # Updates each given attribute to the current time.
159
+ #
160
+ # Assumes that each column can accept a +Time+ instance, i.e. that they're all +datetime+ columns or similar.
161
+ #
162
+ # The updates are done with update_attribute, and as such they are done with callbacks but
163
+ # without validation.
164
+ def touch *attrs
165
+ now = Time.now
166
+ attrs.each {|attribute|
167
+ update_attribute attribute, now
168
+ }
169
+ end
170
+
171
+ # Updates each given attribute to the current time, skipping attributes that are already set.
172
+ #
173
+ # Assumes that each column can accept a +Time+ instance, i.e. that they're all +datetime+ columns or similar.
174
+ #
175
+ # The updates are done with update_attribute, and as such they are done with callbacks but
176
+ # without validation.
177
+ def touch_once *attrs
178
+ touch *attrs.select {|attribute| attributes[attribute.to_s].nil? }
179
+ end
180
+
181
+ def adjust attrs
182
+ attrs.each {|k,v| send "#{k}=", v }
183
+ save false
184
+ end
185
+
186
+ def unsaved_attributes
187
+ self.changed.inject({}) {|hsh,k|
188
+ hsh[k] = attributes[k]
189
+ hsh
190
+ }
191
+ end
192
+
193
+ # Offset +attribute+ by +offset+ atomically in SQL.
194
+ def offset! attribute, offset
195
+ if new_record?
196
+ log "Can't offset! a new record."
197
+ else
198
+ # Update the in-memory model
199
+ send "#{attribute}=", send(attribute) + offset
200
+ # Update the DB
201
+ run_updater_sql 'Offset', "#{connection.quote_column_name(attribute)} = #{connection.quote_column_name(attribute)} + #{quote_value(offset)}"
202
+ end
203
+ end
204
+
205
+
206
+ private
207
+
208
+ def run_updater_sql logger_prefix, set_clause
209
+ connection.update(
210
+ "UPDATE #{self.class.table_name} " +
211
+ "SET #{set_clause} " +
212
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
213
+
214
+ "#{self.class.name} #{logger_prefix}"
215
+ ) != false
216
+ end
217
+
218
+ # TODO Use ambition for association queries.
219
+ # def self.collection_reader_method reflection, association_proxy_class
220
+ # define_method(reflection.name) do |*params|
221
+ # reflection.klass.ambition_context.select { |entity| entity.__send__(reflection.primary_key_name) == quoted_id }
222
+ # end
223
+ # end
224
+
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,73 @@
1
+ module Hammock
2
+ module ArrayPatches
3
+ MixInto = Array
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ # Returns true iff +other+ appears exactly at the start of +self+.
16
+ def starts_with? *other
17
+ self[0, other.length] == other
18
+ end
19
+
20
+ # Returns true iff +other+ appears exactly at the end of +self+.
21
+ def ends_with? *other
22
+ self[-other.length, other.length] == other
23
+ end
24
+
25
+ def squash
26
+ self.dup.squash!
27
+ end
28
+ def squash!
29
+ self.delete_if &:blank?
30
+ end
31
+
32
+ def discard *args
33
+ self.dup.discard! *args
34
+ end
35
+ def discard! *args
36
+ args.each {|arg| self.delete arg }
37
+ self
38
+ end
39
+
40
+ def as_index_for &value_function
41
+ inject({}) do |accum, elem|
42
+ accum[elem] = value_function.call(elem)
43
+ accum
44
+ end
45
+ end
46
+
47
+ def remove_framework_backtrace
48
+ reverse.drop_while {|step|
49
+ !step.starts_with?(RAILS_ROOT)
50
+ }.reverse
51
+ end
52
+
53
+ def hash_by *methods, &block
54
+ hsh = Hash.new {|h,k| h[k] = [] }
55
+ this_method = methods.shift
56
+
57
+ # First, hash this array into +hsh+.
58
+ self.each {|i| hsh[i.send(this_method)] << i }
59
+
60
+ if methods.empty?
61
+ # If there are no methods remaining, yield this group to the block if required.
62
+ hsh.each_pair {|k,v| hsh[k] = yield(hsh[k]) } if block_given?
63
+ else
64
+ # Recursively hash remaining methods.
65
+ hsh.each_pair {|k,v| hsh[k] = v.hash_by(*methods, &block) }
66
+ end
67
+
68
+ hsh
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,49 @@
1
+ module Hammock
2
+ module HashPatches
3
+ MixInto = Hash
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ def discard! *keys
16
+ keys.each {|k| delete k }
17
+ self
18
+ end
19
+
20
+ def discard *keys
21
+ dup.discard! *keys
22
+ end
23
+
24
+ def dragnet *keys
25
+ dup.dragnet! *keys
26
+ end
27
+
28
+ def dragnet! *keys
29
+ keys.inject({}) {|acc,key|
30
+ acc[key] = self.delete(key) if self.has_key?(key)
31
+ acc
32
+ }
33
+ end
34
+
35
+ def to_param_hash prefix = ''
36
+ hsh = self.dup
37
+ # TODO these two blocks can probably be combined
38
+ hsh.keys.each {|k| hsh.merge!(hsh.delete(k).to_param_hash(k)) if hsh[k].is_a?(Hash) }
39
+ hsh.keys.each {|k| hsh["#{prefix}[#{k}]"] = hsh.delete(k) } unless prefix.blank?
40
+ hsh
41
+ end
42
+
43
+ def to_flattened_json
44
+ to_param_hash.to_json
45
+ end
46
+
47
+ end
48
+ end
49
+ end