hobo 0.7.2 → 0.7.3

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 (77) hide show
  1. data/bin/hobo +24 -7
  2. data/hobo_files/plugin/CHANGES.txt +501 -0
  3. data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
  4. data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
  5. data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
  6. data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
  7. data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
  8. data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
  9. data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
  10. data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
  11. data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
  12. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
  13. data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
  14. data/hobo_files/plugin/init.rb +45 -13
  15. data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
  16. data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
  17. data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
  18. data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
  19. data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
  20. data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
  21. data/hobo_files/plugin/lib/hobo.rb +38 -60
  22. data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
  23. data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
  24. data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
  25. data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
  26. data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
  27. data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
  28. data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
  29. data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
  30. data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
  31. data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
  32. data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
  33. data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
  34. data/hobo_files/plugin/lib/hobo/model.rb +236 -429
  35. data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
  36. data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
  37. data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
  38. data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
  39. data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
  40. data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
  41. data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
  42. data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
  43. data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
  44. data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
  45. data/hobo_files/plugin/lib/hobo/user.rb +31 -14
  46. data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
  47. data/hobo_files/plugin/taglibs/core.dryml +9 -11
  48. data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
  49. data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
  50. data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
  51. data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
  52. data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
  53. data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
  54. data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
  55. data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
  56. data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
  57. metadata +60 -55
  58. data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
  59. data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
  60. data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
  61. data/hobo_files/plugin/lib/extensions.rb +0 -375
  62. data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
  63. data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
  64. data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
  65. data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
  66. data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
  67. data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
  68. data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
  69. data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
  70. data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
  71. data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
  72. data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
  73. data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
  74. data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
  75. data/hobo_files/plugin/lib/hobo/text.rb +0 -3
  76. data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
  77. data/hobo_files/plugin/lib/hobo/where_fragment.rb +0 -28
@@ -2,33 +2,34 @@ module Hobo
2
2
 
3
3
  module Model
4
4
 
5
+ class PermissionDeniedError < RuntimeError; end
6
+ class NoNameError < RuntimeError; end
7
+
5
8
  NAME_FIELD_GUESS = %w(name title)
6
9
  PRIMARY_CONTENT_GUESS = %w(description body content profile)
7
10
  SEARCH_COLUMNS_GUESS = %w(name title body content profile)
8
11
 
9
- PLAIN_TYPES = { :boolean => TrueClass,
10
- :date => Date,
11
- :datetime => Time,
12
- :integer => Fixnum,
13
- :big_integer => BigDecimal,
14
- :float => Float,
15
- :string => String
16
- }
17
-
18
- Hobo.field_types.update(PLAIN_TYPES)
19
12
 
20
13
  def self.included(base)
21
- Hobo.register_model(base)
22
14
  base.extend(ClassMethods)
15
+
16
+ included_in_class_callbacks(base)
17
+
18
+ Hobo.register_model(base)
19
+
20
+ patch_will_paginate
21
+
23
22
  base.class_eval do
23
+ inheriting_cattr_reader :default_order
24
24
  alias_method_chain :attributes=, :hobo_type_conversion
25
- default_scopes
26
25
  end
26
+
27
27
  class << base
28
- alias_method_chain :has_many, :defined_scopes
29
- alias_method_chain :belongs_to, :foreign_key_declaration
30
- alias_method_chain :belongs_to, :hobo_metadata
31
- alias_method_chain :acts_as_list, :fields if defined?(ActiveRecord::Acts::List)
28
+ alias_method_chain :has_many, :defined_scopes
29
+ alias_method_chain :belongs_to, :creator_metadata
30
+
31
+ alias_method_chain :has_one, :new_method
32
+
32
33
  def inherited(klass)
33
34
  fields do
34
35
  Hobo.register_model(klass)
@@ -36,126 +37,136 @@ module Hobo
36
37
  end
37
38
  end
38
39
  end
40
+
41
+ end
42
+
43
+ def self.patch_will_paginate
44
+ if defined?(WillPaginate) && !WillPaginate::Collection.respond_to?(:member_class)
45
+
46
+ WillPaginate::Collection.class_eval do
47
+ attr_accessor :member_class, :origin, :origin_attribute
48
+ end
49
+
50
+ WillPaginate::Finder::ClassMethods.class_eval do
51
+ def paginate_with_hobo_metadata(*args, &block)
52
+ returning paginate_without_hobo_metadata(*args, &block) do |collection|
53
+ collection.member_class = self
54
+ collection.origin = try.proxy_owner
55
+ collection.origin_attribute = try.proxy_reflection._?.name
56
+ end
57
+ end
58
+ alias_method_chain :paginate, :hobo_metadata
59
+
60
+ end
61
+
62
+ end
39
63
  end
40
64
 
65
+
41
66
  module ClassMethods
42
67
 
43
68
  # include methods also shared by CompositeModel
44
- include ModelSupport::ClassMethods
69
+ #include ModelSupport::ClassMethods
45
70
 
46
71
  attr_accessor :creator_attribute
47
72
  attr_writer :name_attribute, :primary_content_attribute
48
73
 
49
- def default_scopes
50
- def_scope :recent do |*args|
51
- count = args.first || 3
52
- { :limit => count, :order => "#{table_name}.created_at DESC" }
53
- end
74
+ def named(*args)
75
+ raise NoNameError, "Model #{name} has no name attribute" unless name_attribute
76
+ send("find_by_#{name_attribute}", *args)
77
+ end
78
+
79
+ alias_method :[], :named
80
+
81
+
82
+ def field_added(name, type, args, options)
83
+ self.name_attribute = name.to_sym if options.delete(:name)
84
+ self.primary_content_attribute = name.to_sym if options.delete(:primary_content)
85
+ self.creator_attribute = name.to_sym if options.delete(:creator)
86
+ validate = options.delete(:validate) {true}
87
+
88
+ #FIXME - this should be in Hobo::User
89
+ send(:login_attribute=, name.to_sym, validate) if options.delete(:login) && respond_to?(:login_attribute=)
90
+ end
91
+
92
+
93
+ def user_find(user, *args)
94
+ record = find(*args)
95
+ raise PermissionDeniedError unless Hobo.can_view?(user, self)
96
+ record
54
97
  end
55
98
 
99
+
100
+ def user_new(user, attributes={})
101
+ record = new(attributes)
102
+ record.user_changes(user) and record
103
+ end
104
+
105
+
106
+ def user_new!(user, attributes={})
107
+ user_new(user, attributes) or raise PermissionDeniedError
108
+ end
109
+
110
+
111
+ def user_create(user, attributes={})
112
+ record = new(attributes)
113
+ record.user_save_changes(user)
114
+ record
115
+ end
116
+
117
+
118
+ def user_can_create?(user, attributes={})
119
+ record = new(attributes)
120
+ record.user_changes(user)
121
+ end
122
+
123
+
56
124
  def name_attribute
57
125
  @name_attribute ||= begin
58
- cols = columns.every :name
59
- NAME_FIELD_GUESS.detect {|f| f.in? columns.every(:name) }
126
+ cols = columns.*.name
127
+ NAME_FIELD_GUESS.detect {|f| f.in? columns.*.name }
60
128
  end
61
129
  end
62
130
 
63
131
 
64
132
  def primary_content_attribute
65
- @description_attribute ||= begin
66
- cols = columns.every :name
67
- PRIMARY_CONTENT_GUESS.detect {|f| f.in? columns.every(:name) }
68
- end
133
+ @primary_content_attribute ||= begin
134
+ cols = columns.*.name
135
+ PRIMARY_CONTENT_GUESS.detect {|f| f.in? columns.*.name }
136
+ end
69
137
  end
70
138
 
71
139
  def dependent_collections
72
140
  reflections.values.select do |refl|
73
141
  refl.macro == :has_many && refl.options[:dependent]
74
- end.every(:name)
142
+ end.*.name
75
143
  end
76
144
 
77
145
 
78
146
  def dependent_on
79
147
  reflections.values.select do |refl|
80
148
  refl.macro == :belongs_to && (rev = reverse_reflection(refl.name) and rev.options[:dependent])
81
- end.every(:name)
82
- end
83
-
84
- private
85
-
86
- def return_type(type)
87
- @next_method_type = type
88
- end
89
-
90
- def method_added(name)
91
- if @next_method_type
92
- set_field_type(name => @next_method_type)
93
- @next_method_type = nil
94
- end
149
+ end.*.name
95
150
  end
96
151
 
97
152
 
98
- def fields(&b)
99
- dsl = FieldDeclarationsDsl.new(self)
100
- if b.arity == 1
101
- yield dsl
102
- else
103
- dsl.instance_eval(&b)
104
- end
153
+ def default_dependent_on
154
+ dependent_on.first
105
155
  end
106
156
 
107
157
 
108
- def belongs_to_with_foreign_key_declaration(name, *args, &block)
109
- options = args.extract_options!
110
- res = belongs_to_without_foreign_key_declaration(name, *args + [options], &block)
111
- refl = reflections[name]
112
- fkey = refl.primary_key_name
113
- column_options = {}
114
- column_options[:null] = options[:null] if options.has_key?(:null)
115
- field_specs[fkey] ||= FieldSpec.new(self, fkey, :integer, column_options)
116
- if refl.options[:polymorphic]
117
- type_col = "#{name}_type"
118
- field_specs[type_col] ||= FieldSpec.new(self, type_col, :string, column_options)
119
- end
120
- res
121
- end
122
-
158
+ private
123
159
 
124
- def belongs_to_with_hobo_metadata(name, *args, &block)
125
- options = args.extract_options!
160
+
161
+ def belongs_to_with_creator_metadata(name, options={}, &block)
126
162
  self.creator_attribute = name.to_sym if options.delete(:creator)
127
- belongs_to_without_hobo_metadata(name, *args + [options], &block)
163
+ belongs_to_without_creator_metadata(name, options, &block)
128
164
  end
129
-
130
-
131
- def acts_as_list_with_fields(options = {})
132
- fields { |f| f.send(options._?[:column] || "position", :integer) }
133
- acts_as_list_without_fields(options)
134
- end
135
-
136
-
137
- def field_specs
138
- @field_specs ||= HashWithIndifferentAccess.new
139
- end
140
- public :field_specs
141
165
 
142
- def set_field_type(types)
143
- types.each_pair do |field, type|
144
- type_class = Hobo.field_types[type] || type
145
- field_types[field] = type_class
146
-
147
- if type_class && "validate".in?(type_class.instance_methods)
148
- self.validate do |record|
149
- v = record.send(field)._?.validate
150
- record.errors.add(field, v) if v.is_a?(String)
151
- end
152
- end
153
- end
154
- end
155
-
156
-
157
- def field_types
158
- @hobo_field_types ||= superclass.respond_to?(:field_types) ? superclass.field_types : {}
166
+
167
+ def has_one_with_new_method(name, options={}, &block)
168
+ has_one_without_new_method(name, options)
169
+ class_eval "def new_#{name}(attributes={}); build_#{name}(attributes, false); end"
159
170
  end
160
171
 
161
172
 
@@ -163,178 +174,62 @@ module Hobo
163
174
  @default_order = order
164
175
  end
165
176
 
166
- inheriting_attr_accessor :default_order, :id_name_options
167
-
168
177
 
169
178
  def never_show(*fields)
170
179
  @hobo_never_show ||= []
171
- @hobo_never_show.concat(fields.every(:to_sym))
172
- end
173
-
174
- def never_show?(field)
175
- (@hobo_never_show && field.to_sym.in?(@hobo_never_show)) || (superclass < Hobo::Model && superclass.never_show?(field))
180
+ @hobo_never_show.concat(fields.*.to_sym)
176
181
  end
177
- public :never_show?
178
182
 
183
+
179
184
  def set_search_columns(*columns)
180
185
  class_eval %{
181
186
  def self.search_columns
182
- %w{#{columns.every(:to_s) * ' '}}
187
+ %w{#{columns.*.to_s * ' '}}
183
188
  end
184
189
  }
185
190
  end
186
191
 
187
-
188
- def id_name(*args)
189
- @id_name_options = [] + args
190
-
191
- underscore = args.delete(:underscore)
192
- insenstive = args.delete(:case_insensitive)
193
- id_name_field = args.first || :name
194
- @id_name_column = id_name_field.to_s
195
-
196
- if underscore
197
- class_eval %{
198
- def id_name(underscore=false)
199
- underscore ? #{id_name_field}.gsub(' ', '_') : #{id_name_field}
200
- end
201
- }
202
- else
203
- class_eval %{
204
- def id_name(underscore=false)
205
- #{id_name_field}
206
- end
207
- }
208
- end
209
-
210
- key = "id_name#{if underscore; ".gsub('_', ' ')"; end}"
211
- finder = if insenstive
212
- "find(:first, options.merge(:conditions => ['lower(#{id_name_field}) = ?', #{key}.downcase]))"
213
- else
214
- "find_by_#{id_name_field}(#{key}, options)"
215
- end
216
-
217
- class_eval %{
218
- def self.find_by_id_name(id_name, options={})
219
- #{finder}
220
- end
221
- }
222
-
223
- model = self
224
- validate do
225
- erros.add id_name_field, "is taken" if model.find_by_id_name(name)
226
- end
227
- validates_format_of id_name_field, :with => /^[^_]+$/, :message => "cannot contain underscores" if
228
- underscore
229
- end
230
-
231
- public
232
192
 
233
- def id_name?
234
- respond_to?(:find_by_id_name)
235
- end
236
-
237
- attr_reader :id_name_column
193
+ public
238
194
 
239
195
 
240
- def field_type(name)
241
- name = name.to_sym
242
- field_types[name] or
243
- reflections[name] or begin
244
- col = column(name)
245
- return nil if col.nil?
246
- case col.type
247
- when :boolean
248
- TrueClass
249
- when :text
250
- Hobo::Text
251
- else
252
- col.klass
253
- end
254
- end
255
- end
256
-
257
-
258
- def column(name)
259
- columns.find {|c| c.name == name.to_s} rescue nil
260
- end
261
-
262
-
263
- def conditions(*args, &b)
264
- if args.empty?
265
- ModelQueries.new(self).instance_eval(&b)._?.to_sql
266
- else
267
- ModelQueries.new(self).instance_exec(*args, &b)._?.to_sql
268
- end
196
+ def never_show?(field)
197
+ (@hobo_never_show && field.to_sym.in?(@hobo_never_show)) || (superclass < Hobo::Model && superclass.never_show?(field))
269
198
  end
270
199
 
271
200
 
272
201
  def find(*args, &b)
273
202
  options = args.extract_options!
274
- if args.first.in?([:all, :first]) && options[:order] == :default
203
+ if options[:order] == :default
275
204
  options = if default_order.blank?
276
- options - [:order]
205
+ options.except :order
277
206
  else
278
207
  options.merge(:order => "#{table_name}.#{default_order}")
279
208
  end
280
209
  end
281
-
282
- res = if b && !(block_conditions = conditions(&b)).blank?
283
- c = if !options[:conditions].blank?
284
- "(#{sanitize_sql options[:conditions]}) AND (#{sanitize_sql block_conditions})"
285
- else
286
- block_conditions
287
- end
288
- super(args.first, options.merge(:conditions => c))
289
- else
290
- super(*args + [options])
291
- end
292
- if args.first == :all
293
- def res.member_class
294
- @member_class
295
- end
296
- res.instance_variable_set("@member_class", self)
297
- end
298
- res
210
+ result = super(*args + [options])
211
+ result.member_class = self if result.is_a?(Array)
212
+ result
299
213
  end
300
-
214
+
301
215
 
302
216
  def all(options={})
303
217
  find(:all, options.reverse_merge(:order => :default))
304
218
  end
305
219
 
306
220
 
307
- def count(*args, &b)
308
- if b
309
- sql = ModelQueries.new(self).instance_eval(&b).to_sql
310
- options = extract_options_from_args!(args)
311
- super(*args + [options.merge(:conditions => sql)])
312
- else
313
- super(*args)
314
- end
315
- end
316
-
317
-
318
- def subclass_associations(association, *subclass_associations)
319
- refl = reflections[association]
320
- for assoc in subclass_associations
321
- class_name = assoc.to_s.classify
322
- options = { :class_name => class_name, :conditions => "type = '#{class_name}'" }
323
- options[:source] = refl.source_reflection.name if refl.source_reflection
324
- has_many(assoc, refl.options.merge(options))
325
- end
326
- end
327
-
328
221
  def creator_type
329
222
  reflections[creator_attribute]._?.klass
330
223
  end
331
224
 
225
+
332
226
  def search_columns
333
- cols = columns.every(:name)
227
+ cols = columns.*.name
334
228
  SEARCH_COLUMNS_GUESS.select{|c| c.in?(cols) }
335
229
  end
336
230
 
337
- # This should really be a method on AssociationReflection
231
+
232
+ # FIXME: This should really be a method on AssociationReflection
338
233
  def reverse_reflection(association_name)
339
234
  refl = reflections[association_name]
340
235
  return nil if refl.options[:conditions]
@@ -353,153 +248,94 @@ module Hobo
353
248
  end
354
249
 
355
250
 
356
- class ScopedProxy
357
-
358
- def initialize(klass, scope)
359
- @klass = klass
360
-
361
- # If there's no :find, or :create specified, assume it's a find scope
362
- @scope = if scope.has_key?(:find) || scope.has_key?(:create)
363
- scope
364
- else
365
- { :find => scope }
366
- end
367
- end
368
-
369
-
370
- def method_missing(name, *args, &block)
371
- if name.to_sym.in?(@klass.defined_scopes.keys)
372
- proxy = @klass.send(name, *args)
373
- proxy.instance_variable_set("@parent_scope", self)
374
- proxy
375
- else
376
- _apply_scope { @klass.send(name, *args, &block) }
377
- end
378
- end
379
-
380
- def all
381
- self.find(:all)
382
- end
383
-
384
- def first
385
- self.find(:first)
386
- end
387
-
388
- def member_class
389
- @klass
390
- end
391
-
392
- private
393
- def _apply_scope
394
- if @parent_scope
395
- @parent_scope.send(:_apply_scope) do
396
- @scope ? @klass.send(:with_scope, @scope) { yield } : yield
397
- end
398
- else
399
- @scope ? @klass.send(:with_scope, @scope) { yield } : yield
400
- end
401
- end
402
-
403
- end
404
- (Object.instance_methods +
405
- Object.private_instance_methods +
406
- Object.protected_instance_methods).each do |m|
407
- ScopedProxy.send(:undef_method, m) unless
408
- m.in?(%w{initialize method_missing send instance_variable_set instance_variable_get puts}) || m.starts_with?('_')
251
+ def has_inheritance_column?
252
+ columns_hash.include?(inheritance_column)
409
253
  end
410
254
 
411
- attr_accessor :defined_scopes
412
255
 
413
-
414
- def def_scope(name, scope=nil, &block)
415
- @defined_scopes ||= {}
416
- @defined_scopes[name.to_sym] = block || scope
417
-
418
- meta_def(name) do |*args|
419
- ScopedProxy.new(self, block ? block.call(*args) : scope)
256
+ def method_missing(name, *args, &block)
257
+ name = name.to_s
258
+ if name =~ /\./
259
+ # FIXME: Do we need this now?
260
+ call_method_chain(name, args, &block)
261
+ elsif create_automatic_scope(name)
262
+ send(name, *args, &block)
263
+ else
264
+ super(name.to_sym, *args, &block)
420
265
  end
421
266
  end
267
+
268
+
269
+ def call_method_chain(chain, args, &block)
270
+ parts = chain.split(".")
271
+ s = parts[0..-2].inject(self) { |m, scope| m.send(scope) }
272
+ s.send(parts.last, *args)
273
+ end
422
274
 
423
275
 
424
- module DefinedScopeProxyExtender
276
+ def to_url_path
277
+ "#{name.underscore.pluralize}"
278
+ end
279
+
280
+ def typed_id
281
+ HoboFields.to_name(self) || name.underscore.gsub("/", "__")
282
+ end
283
+
284
+ end # --- of ClassMethods --- #
285
+
286
+
287
+ include Scopes
288
+
289
+
290
+ def to_url_path
291
+ "#{self.class.to_url_path}/#{to_param}" unless new_record?
292
+ end
293
+
294
+
295
+ def user_changes(user, changes={})
296
+ if new_record?
297
+ self.attributes = changes
298
+ set_creator(user)
299
+ Hobo.can_create?(user, self)
300
+ else
301
+ original = duplicate
302
+ # 'duplicate' can cause these to be set, but they can conflict
303
+ # with the changes so we clear them
304
+ clear_aggregation_cache
305
+ clear_association_cache
425
306
 
426
- attr_accessor :reflections
307
+ self.attributes = changes
427
308
 
428
- def method_missing(name, *args, &block)
429
- scope = (proxy_reflection.klass.respond_to?(:defined_scopes) and
430
- scopes = proxy_reflection.klass.defined_scopes and
431
- scopes[name.to_sym])
432
-
433
- scope = scope.call(*args) if scope.is_a?(Proc)
434
-
435
- # If there's no :find, or :create specified, assume it's a find scope
436
- find_scope = if scope && (scope.has_key?(:find) || scope.has_key?(:create))
437
- scope[:find]
438
- else
439
- scope
440
- end
441
-
442
- if find_scope
443
- scope_name = "@#{name.to_s.gsub('?','')}_scope"
444
-
445
- # Calling instance_variable_get directly causes self to
446
- # get loaded, hence this trick
447
- assoc = Kernel.instance_method(:instance_variable_get).bind(self).call(scope_name)
448
-
449
- unless assoc
450
- options = proxy_reflection.options
451
- has_many_conditions = options[:conditions]
452
- has_many_conditions = nil if has_many_conditions.blank?
453
- source = proxy_reflection.source_reflection
454
- scope_conditions = find_scope[:conditions]
455
- scope_conditions = nil if scope_conditions.blank?
456
- conditions = if has_many_conditions && scope_conditions
457
- "(#{sanitize_sql scope_conditions}) AND (#{sanitize_sql has_many_conditions})"
458
- else
459
- scope_conditions || has_many_conditions
460
- end
461
-
462
- options = options.merge(find_scope).update(:class_name => proxy_reflection.klass.name,
463
- :foreign_key => proxy_reflection.primary_key_name)
464
- options[:conditions] = conditions unless conditions.blank?
465
- options[:source] = source.name if source
309
+ Hobo.can_update?(user, original, self)
310
+ end
311
+ end
312
+
313
+
314
+ def user_changes!(user, changes={})
315
+ user_changes(user, changes) or raise PermissionDeniedError
316
+ end
317
+
318
+
319
+ def user_can_create?(user, attributes={})
320
+ raise ArgumentError, "Called #user_can_create? on existing record" unless new_record?
321
+ user_changes(user, attributes)
322
+ end
323
+
466
324
 
467
- r = ActiveRecord::Reflection::AssociationReflection.new(:has_many,
468
- name,
469
- options,
470
- proxy_owner.class)
471
-
472
- @reflections ||= {}
473
- @reflections[name] = r
474
-
475
- assoc = if source
476
- ActiveRecord::Associations::HasManyThroughAssociation
477
- else
478
- ActiveRecord::Associations::HasManyAssociation
479
- end.new(self.proxy_owner, r)
325
+ def user_save_changes(user, changes={})
326
+ user_changes!(user, changes)
327
+ save
328
+ end
329
+
480
330
 
481
- # Calling directly causes self to get loaded
482
- Kernel.instance_method(:instance_variable_set).bind(self).call(scope_name, assoc)
483
- end
484
- assoc
485
- else
486
- super
487
- end
488
- end
489
-
490
- end
491
-
492
-
493
- def has_many_with_defined_scopes(name, *args, &block)
494
- options = args.extract_options!
495
- if options.has_key?(:extend) || block
496
- # Normal has_many
497
- has_many_without_defined_scopes(name, *args + [options], &block)
498
- else
499
- options[:extend] = DefinedScopeProxyExtender
500
- has_many_without_defined_scopes(name, *args + [options], &block)
501
- end
502
- end
331
+ def user_view(user, field=nil)
332
+ raise PermissionDeniedError unless Hobo.can_view?(user, self, field)
333
+ end
334
+
335
+
336
+ def user_destroy(user)
337
+ raise PermissionDeniedError unless Hobo.can_delete?(user, self)
338
+ destroy
503
339
  end
504
340
 
505
341
 
@@ -509,13 +345,18 @@ module Hobo
509
345
 
510
346
 
511
347
  def attributes_with_hobo_type_conversion=(attributes, guard_protected_attributes=true)
512
- converted = attributes.map_hash { |k, v| convert_type_for_mass_assignment(self.class.field_type(k), v) }
348
+ converted = attributes.map_hash { |k, v| convert_type_for_mass_assignment(self.class.attr_type(k), v) }
513
349
  send(:attributes_without_hobo_type_conversion=, converted, guard_protected_attributes)
514
350
  end
515
351
 
516
352
 
517
353
 
518
354
  def set_creator(user)
355
+ set_creator!(user) unless get_creator
356
+ end
357
+
358
+
359
+ def set_creator!(user)
519
360
  attr = self.class.creator_attribute
520
361
  return unless attr
521
362
 
@@ -528,22 +369,30 @@ module Hobo
528
369
  self.send("#{attr}=", user.to_s) unless user.guest?
529
370
  end
530
371
  end
372
+
373
+
374
+ # We deliberately give this method an unconventional name to avoid
375
+ # polluting the application namespace too badly
376
+ def get_creator
377
+ self.class.creator_attribute && send(self.class.creator_attribute)
378
+ end
531
379
 
532
380
 
533
381
  def duplicate
534
- res = self.class.new
535
- res.instance_variable_set("@attributes", @attributes.dup)
536
- res.instance_variable_set("@new_record", nil) unless new_record?
382
+ copy = self.class.new
383
+ copy.copy_instance_variables_from(self, ["@attributes_cache"])
384
+ copy.instance_variable_set("@attributes", @attributes.dup)
385
+ copy.instance_variable_set("@new_record", nil) unless new_record?
537
386
 
538
387
  # Shallow copy of belongs_to associations
539
388
  for refl in self.class.reflections.values
540
389
  if refl.macro == :belongs_to and (target = self.send(refl.name))
541
- bta = ActiveRecord::Associations::BelongsToAssociation.new(res, refl)
390
+ bta = ActiveRecord::Associations::BelongsToAssociation.new(copy, refl)
542
391
  bta.replace(target)
543
- res.instance_variable_set("@#{refl.name}", bta)
392
+ copy.instance_variable_set("@#{refl.name}", bta)
544
393
  end
545
394
  end
546
- res
395
+ copy
547
396
  end
548
397
 
549
398
 
@@ -554,29 +403,25 @@ module Hobo
554
403
  fields.all?{|f| self.send(f) == other.send(f)}
555
404
  end
556
405
 
406
+
557
407
  def only_changed_fields?(other, *changed_fields)
558
408
  return true if other.nil?
559
409
 
560
- changed_fields = changed_fields.flatten.every(:to_s)
561
- all_cols = self.class.columns.every(:name) - []
410
+ changed_fields = changed_fields.flatten.*.to_s
411
+ all_cols = self.class.columns.*.name - []
562
412
  all_cols.all?{|c| c.in?(changed_fields) || self.send(c) == other.send(c) }
563
413
  end
564
414
 
415
+
565
416
  def compose_with(object, use=nil)
566
417
  CompositeModel.new_for([self, object])
567
418
  end
568
419
 
569
- def created_date
570
- created_at.to_date
571
- end
572
-
573
- def modified_date
574
- modified_at.to_date
575
- end
576
420
 
577
421
  def typed_id
578
- id ? "#{self.class.name.underscore}_#{self.id}" : nil
422
+ "#{self.class.name.underscore}_#{self.id}" if id
579
423
  end
424
+
580
425
 
581
426
  def to_s
582
427
  if self.class.name_attribute
@@ -586,15 +431,20 @@ module Hobo
586
431
  end
587
432
  end
588
433
 
434
+
589
435
  private
590
436
 
437
+
591
438
  def parse_datetime(s)
592
439
  defined?(Chronic) ? Chronic.parse(s) : Time.parse(s)
593
440
  end
594
441
 
442
+
595
443
  def convert_type_for_mass_assignment(field_type, value)
596
- if field_type.is_a?(ActiveRecord::Reflection::AssociationReflection) and field_type.macro.in?([:belongs_to, :has_one])
444
+ if field_type.is_a?(ActiveRecord::Reflection::AssociationReflection) &&
445
+ field_type.macro.in?([:belongs_to, :has_one])
597
446
  if value.is_a?(String) && value.starts_with?('@')
447
+ # TODO: This @foo_1 feature is rarely (never?) used - get rid of it
598
448
  Hobo.object_from_dom_id(value[1..-1])
599
449
  else
600
450
  value
@@ -618,62 +468,19 @@ module Hobo
618
468
  else
619
469
  value
620
470
  end
621
- elsif field_type <= TrueClass
471
+ elsif field_type <= Hobo::Boolean
622
472
  (value.is_a?(String) && value.strip.downcase.in?(['0', 'false']) || value.blank?) ? false : true
623
473
  else
624
474
  # primitive field
625
475
  value
626
476
  end
627
477
  end
628
-
629
- end
630
- end
631
-
632
-
633
- # Hack AR to get Hobo type wrappers in
634
-
635
- module ActiveRecord::AttributeMethods::ClassMethods
636
-
637
- # Define an attribute reader method. Cope with nil column.
638
- def define_read_method(symbol, attr_name, column)
639
- cast_code = column.type_cast_code('v') if column
640
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
641
-
642
- unless attr_name.to_s == self.primary_key.to_s
643
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) " +
644
- "unless @attributes.has_key?('#{attr_name}'); ")
645
- end
646
-
647
- # This is the Hobo hook - add a type wrapper around the field
648
- # value if we have a special type defined
649
- src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(symbol)) &&
650
- type_wrapper.is_a?(Class) && type_wrapper.not_in?(Hobo::Model::PLAIN_TYPES.values)
651
- "val = begin; #{access_code}; end; " +
652
- "if val.nil? || (val.respond_to?(:hobo_undefined?) && val.hobo_undefined?); val; " +
653
- "else; self.class.field_type(:#{attr_name}).new(val); end"
654
- else
655
- access_code
656
- end
657
-
658
- evaluate_attribute_method(attr_name,
659
- "def #{symbol}; @attributes_cache['#{attr_name}'] ||= begin; #{src}; end; end")
478
+
660
479
  end
661
480
 
662
- def define_write_method(attr_name)
663
- src = if connected? && respond_to?(:field_type) && (type_wrapper = field_type(attr_name)) &&
664
- type_wrapper.is_a?(Class) && type_wrapper.not_in?(Hobo::Model::PLAIN_TYPES.values)
665
- "if val.nil? || (val.respond_to?(:hobo_undefined?) && val.hobo_undefined?); val; " +
666
- "else; self.class.field_type(:#{attr_name}).new(val); end"
667
- else
668
- "val"
669
- end
670
- evaluate_attribute_method(attr_name, "def #{attr_name}=(val); " +
671
- "write_attribute('#{attr_name}', #{src});end", "#{attr_name}=")
672
-
673
- end
674
-
675
481
  end
676
482
 
483
+
677
484
  class ActiveRecord::Base
678
485
  alias_method :has_hobo_method?, :respond_to_without_attributes?
679
486
  end