hammock 0.2.11.2

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 (47) hide show
  1. data/History.txt +67 -0
  2. data/LICENSE +24 -0
  3. data/Manifest.txt +46 -0
  4. data/README.rdoc +105 -0
  5. data/Rakefile +29 -0
  6. data/hammock.gemspec +43 -0
  7. data/lib/hammock/ajaxinate.rb +154 -0
  8. data/lib/hammock/callback.rb +16 -0
  9. data/lib/hammock/callbacks.rb +94 -0
  10. data/lib/hammock/canned_scopes.rb +125 -0
  11. data/lib/hammock/constants.rb +9 -0
  12. data/lib/hammock/controller_attributes.rb +52 -0
  13. data/lib/hammock/export_scope.rb +74 -0
  14. data/lib/hammock/hamlink_to.rb +47 -0
  15. data/lib/hammock/javascript_buffer.rb +63 -0
  16. data/lib/hammock/logging.rb +98 -0
  17. data/lib/hammock/model_attributes.rb +53 -0
  18. data/lib/hammock/model_logging.rb +30 -0
  19. data/lib/hammock/monkey_patches/action_pack.rb +32 -0
  20. data/lib/hammock/monkey_patches/active_record.rb +231 -0
  21. data/lib/hammock/monkey_patches/array.rb +73 -0
  22. data/lib/hammock/monkey_patches/hash.rb +57 -0
  23. data/lib/hammock/monkey_patches/logger.rb +28 -0
  24. data/lib/hammock/monkey_patches/module.rb +27 -0
  25. data/lib/hammock/monkey_patches/numeric.rb +25 -0
  26. data/lib/hammock/monkey_patches/object.rb +61 -0
  27. data/lib/hammock/monkey_patches/route_set.rb +28 -0
  28. data/lib/hammock/monkey_patches/string.rb +197 -0
  29. data/lib/hammock/overrides.rb +36 -0
  30. data/lib/hammock/resource_mapping_hooks.rb +28 -0
  31. data/lib/hammock/resource_retrieval.rb +119 -0
  32. data/lib/hammock/restful_actions.rb +172 -0
  33. data/lib/hammock/restful_rendering.rb +114 -0
  34. data/lib/hammock/restful_support.rb +186 -0
  35. data/lib/hammock/route_drawing_hooks.rb +22 -0
  36. data/lib/hammock/route_for.rb +59 -0
  37. data/lib/hammock/route_node.rb +159 -0
  38. data/lib/hammock/route_step.rb +87 -0
  39. data/lib/hammock/scope.rb +127 -0
  40. data/lib/hammock/suggest.rb +36 -0
  41. data/lib/hammock/utils.rb +42 -0
  42. data/lib/hammock.rb +29 -0
  43. data/misc/scaffold.txt +83 -0
  44. data/misc/template.rb +17 -0
  45. data/tasks/hammock_tasks.rake +5 -0
  46. data/test/hammock_test.rb +8 -0
  47. metadata +142 -0
@@ -0,0 +1,231 @@
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 param_key
43
+ "#{base_model}_id"
44
+ end
45
+
46
+ def base_model
47
+ base_class.to_s.underscore
48
+ end
49
+
50
+ def description
51
+ base_model
52
+ end
53
+
54
+ def record?; false end
55
+ def resource?; true end
56
+
57
+ def update_statement set_clause, where_clause
58
+ statement = "UPDATE #{table_name} SET #{set_clause} WHERE #{send :sanitize_sql_array, where_clause}"
59
+ connection.update statement
60
+ end
61
+
62
+ def reset_cached_column_info
63
+ reset_column_information
64
+ subclasses.each &:reset_cached_column_info
65
+ end
66
+
67
+ def find_or_new_with(find_attributes, create_attributes = {})
68
+ finder = respond_to?(:find_with_deleted) ? :find_with_deleted : :find
69
+
70
+ if record = send(finder, :first, :conditions => find_attributes.discard(:deleted_at))
71
+ # Found the record, so we can return it, if:
72
+ # (a) the record can't have a stored deletion state,
73
+ # (b) it can, but it's not actually deleted,
74
+ # (c) it is deleted, but we want to find one that's deleted, or
75
+ # (d) we don't want a deleted record, and undestruction succeeds.
76
+ if (finder != :find_with_deleted) || !record.deleted? || create_attributes[:deleted_at] || record.undestroy
77
+ record
78
+ end
79
+ else
80
+ creating_class = if create_attributes[:type].is_a?(ActiveRecord::Base)
81
+ create_attributes.delete(:type)
82
+ else
83
+ self
84
+ end
85
+ creating_class.new_with create_attributes.merge(find_attributes)
86
+ end
87
+ end
88
+
89
+ def find_or_create_with(find_attributes, create_attributes = {}, adjust_attributes = false)
90
+ if record = find_or_new_with(find_attributes, create_attributes)
91
+ log "Create failed. #{record.errors.inspect}", :skip => 1 if record.new_record? && !record.save
92
+ log "Adjust failed. #{record.errors.inspect}", :skip => 1 if adjust_attributes && !record.adjust(create_attributes)
93
+ record
94
+ end
95
+ end
96
+
97
+ # def find_or_create_with! find_attributes, create_attributes = {}, adjust_attributes = false)
98
+ # record = find_or_new_with find_attributes, create_attributes, adjust_attributes
99
+ # record.valid? ? record : raise("Save failed. #{record.errors.inspect}")
100
+ # end
101
+
102
+ end
103
+
104
+ module InstanceMethods
105
+
106
+ def concise_inspect
107
+ "#{self.class}<#{self.to_param || 'new'}>"
108
+ end
109
+
110
+ def resource
111
+ self.class.resource
112
+ end
113
+
114
+ def resource_sym
115
+ self.class.resource_sym
116
+ end
117
+
118
+ def resource_name
119
+ self.class.resource_name
120
+ end
121
+
122
+ def record?; true end
123
+ def resource?; false end
124
+
125
+ def id_str
126
+ if new_record?
127
+ "new_#{base_model}"
128
+ else
129
+ "#{base_model}_#{id}"
130
+ end
131
+ end
132
+
133
+ def description
134
+ new_record? ? "new_#{base_model}" : "#{base_model}_#{id}"
135
+ end
136
+
137
+ def base_model
138
+ self.class.base_model
139
+ end
140
+
141
+ def new_or_deleted_before_save?
142
+ @new_or_deleted_before_save
143
+ end
144
+ def set_new_or_deleted_before_save
145
+ @new_or_deleted_before_save = new_record? || send_if_respond_to(:deleted?)
146
+ end
147
+
148
+ def undestroy
149
+ unless new_record?
150
+ if frozen?
151
+ self.class.find_with_deleted(self.id).undestroy # Re-fetch ourselves and undestroy the thawed copy
152
+ else
153
+ # We can undestroy
154
+ return false if callback(:before_undestroy) == false
155
+ result = self.class.update_all ['deleted_at = ?', (self.deleted_at = nil)], ['id = ?', self.id]
156
+ callback(:after_undestroy)
157
+ self if result != false
158
+ end
159
+ end
160
+ end
161
+
162
+ # Updates each given attribute to the current time.
163
+ #
164
+ # Assumes that each column can accept a +Time+ instance, i.e. that they're all +datetime+ columns or similar.
165
+ #
166
+ # The updates are done with update_attribute, and as such they are done with callbacks but
167
+ # without validation.
168
+ def touch *attrs
169
+ now = Time.now
170
+ attrs.each {|attribute|
171
+ update_attribute attribute, now
172
+ }
173
+ end
174
+
175
+ # Updates each given attribute to the current time, skipping attributes that are already set.
176
+ #
177
+ # Assumes that each column can accept a +Time+ instance, i.e. that they're all +datetime+ columns or similar.
178
+ #
179
+ # The updates are done with update_attribute, and as such they are done with callbacks but
180
+ # without validation.
181
+ def touch_once *attrs
182
+ touch *attrs.select {|attribute| attributes[attribute.to_s].nil? }
183
+ end
184
+
185
+ def adjust attrs
186
+ attrs.each {|k,v| send "#{k}=", v }
187
+ save false
188
+ end
189
+
190
+ def unsaved_attributes
191
+ self.changed.inject({}) {|hsh,k|
192
+ hsh[k] = attributes[k]
193
+ hsh
194
+ }
195
+ end
196
+
197
+ # Offset +attribute+ by +offset+ atomically in SQL.
198
+ def offset! attribute, offset
199
+ if new_record?
200
+ log "Can't offset! a new record."
201
+ else
202
+ # Update the in-memory model
203
+ send "#{attribute}=", send(attribute) + offset
204
+ # Update the DB
205
+ run_updater_sql 'Offset', "#{connection.quote_column_name(attribute)} = #{connection.quote_column_name(attribute)} + #{quote_value(offset)}"
206
+ end
207
+ end
208
+
209
+
210
+ private
211
+
212
+ def run_updater_sql logger_prefix, set_clause
213
+ connection.update(
214
+ "UPDATE #{self.class.table_name} " +
215
+ "SET #{set_clause} " +
216
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
217
+
218
+ "#{self.class.name} #{logger_prefix}"
219
+ ) != false
220
+ end
221
+
222
+ # TODO Use ambition for association queries.
223
+ # def self.collection_reader_method reflection, association_proxy_class
224
+ # define_method(reflection.name) do |*params|
225
+ # reflection.klass.ambition_context.select { |entity| entity.__send__(reflection.primary_key_name) == quoted_id }
226
+ # end
227
+ # end
228
+
229
+ end
230
+ end
231
+ 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,57 @@
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 selekt &block
25
+ hsh = {}
26
+ each_pair {|k,v|
27
+ hsh[k] = v if yield(k,v)
28
+ }
29
+ hsh
30
+ end
31
+
32
+ def dragnet *keys
33
+ dup.dragnet! *keys
34
+ end
35
+
36
+ def dragnet! *keys
37
+ keys.inject({}) {|acc,key|
38
+ acc[key] = self.delete(key) if self.has_key?(key)
39
+ acc
40
+ }
41
+ end
42
+
43
+ def to_param_hash prefix = ''
44
+ hsh = self.dup
45
+ # TODO these two blocks can probably be combined
46
+ hsh.keys.each {|k| hsh.merge!(hsh.delete(k).to_param_hash(k)) if hsh[k].is_a?(Hash) }
47
+ hsh.keys.each {|k| hsh["#{prefix}[#{k}]"] = hsh.delete(k) } unless prefix.blank?
48
+ hsh
49
+ end
50
+
51
+ def to_flattened_json
52
+ to_param_hash.to_json
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ module Hammock
2
+ module BufferedLoggerPatches
3
+ MixInto = ActiveSupport::BufferedLogger
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 :fatal, :color
11
+ # }
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ def fatal_with_color message = nil, progname = nil, &block
21
+ first_line, other_lines = message.strip.split("\n", 2)
22
+ fatal_without_color "\n" + first_line.colorize('on red'), progname, &block
23
+ fatal_without_color other_lines.colorize('red') + "\n\n", progname, &block
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ module Hammock
2
+ module ModulePatches
3
+ MixInto = Module
4
+ LoadFirst = true
5
+
6
+ def self.included base # :nodoc:
7
+ base.send :include, InstanceMethods
8
+ base.send :extend, ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ end
13
+
14
+ module InstanceMethods
15
+
16
+ def alias_method_chain_once target, feature
17
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
18
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
19
+
20
+ unless [public_instance_methods, protected_instance_methods, private_instance_methods].flatten.include? without_method
21
+ alias_method_chain target, feature
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module Hammock
2
+ module NumericPatches
3
+ MixInto = Numeric
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
+ base.class_eval {
10
+ alias kb kilobytes
11
+ alias mb megabytes
12
+ alias gb gigabytes
13
+ alias tb terabytes
14
+ }
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ end
20
+
21
+ module InstanceMethods
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ module Hammock
2
+ module ObjectPatches
3
+ MixInto = Object
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+
9
+ base.class_eval {
10
+ alias is_an? is_a?
11
+ }
12
+ end
13
+
14
+ module ClassMethods
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ # Return +self+ after yielding to the given block.
20
+ #
21
+ # Useful for inline logging and diagnostics. Consider the following:
22
+ # @items.map {|i| process(i) }.join(", ")
23
+ # With +tap+, adding intermediate logging is simple:
24
+ # @items.map {|i| process(i) }.tap {|obj| log obj.inspect }.join(", ")
25
+ #--
26
+ # TODO Remove for Ruby 1.9
27
+ def tap
28
+ yield self
29
+ self
30
+ end
31
+
32
+ # The reverse of <tt>Enumerable#include?</tt> - returns +true+ if +self+ is
33
+ # equal to one of the elements of +args+.
34
+ def in? *args
35
+ args.include? self
36
+ end
37
+
38
+ # A symbolized, underscored (i.e. reverse-camelized) representation of +self+.
39
+ #
40
+ # Examples:
41
+ #
42
+ # Hash.symbolize #=> :hash
43
+ # ActiveRecord::Base.symbolize #=> :"active_record/base"
44
+ # "GetThisCamelOffMyCase".symbolize #=> :get_this_camel_off_my_case
45
+ def symbolize
46
+ self.to_s.underscore.to_sym
47
+ end
48
+
49
+ # If +condition+ evaluates to true, return the result of sending +method_name+ to +self+; <tt>*args</tt> to +self+, otherwise, return +self+ as-is.
50
+ def send_if condition, method_name, *args
51
+ condition ? send(method_name, *args) : self
52
+ end
53
+
54
+ # If +condition+ evaluates to true, return the result of sending +method_name+ to +self+; <tt>*args</tt> to +self+, otherwise, return +self+ as-is.
55
+ def send_if_respond_to method_name, *args
56
+ send_if respond_to?(method_name), method_name, *args
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,28 @@
1
+ module Hammock
2
+ module RouteSetPatches
3
+ MixInto = ActionController::Routing::RouteSet
4
+
5
+ def self.included base # :nodoc:
6
+ base.send :include, InstanceMethods
7
+ base.send :extend, ClassMethods
8
+
9
+ base.class_eval {
10
+ attr_accessor :route_map
11
+ }
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ def initialize_hammock_route_map
23
+ self.route_map = Hammock::RouteNode.new
24
+ end
25
+
26
+ end
27
+ end
28
+ end