benhoskings-hammock 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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