hobo 0.7.2 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +24 -7
- data/hobo_files/plugin/CHANGES.txt +501 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +8 -6
- data/hobo_files/plugin/generators/hobo/templates/application.dryml +3 -0
- data/hobo_files/plugin/generators/hobo/templates/dryml-support.js +132 -0
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +4 -5
- data/hobo_files/plugin/generators/hobo_model_resource/hobo_model_resource_generator.rb +75 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/controller.rb +7 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/functional_test.rb +8 -0
- data/hobo_files/plugin/generators/hobo_model_resource/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo-rapid.js +30 -11
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/application.css +149 -92
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +0 -48
- data/hobo_files/plugin/init.rb +45 -13
- data/hobo_files/plugin/lib/action_view_extensions/base.rb +4 -3
- data/hobo_files/plugin/lib/active_record/association_proxy.rb +18 -0
- data/hobo_files/plugin/lib/active_record/association_reflection.rb +5 -0
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +7 -11
- data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +8 -0
- data/hobo_files/plugin/lib/extensions/test_case.rb +1 -1
- data/hobo_files/plugin/lib/hobo.rb +38 -60
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +1 -1
- data/hobo_files/plugin/lib/hobo/bundle.rb +131 -34
- data/hobo_files/plugin/lib/hobo/composite_model.rb +1 -1
- data/hobo_files/plugin/lib/hobo/controller.rb +7 -8
- data/hobo_files/plugin/lib/hobo/dev_controller.rb +21 -0
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +14 -8
- data/hobo_files/plugin/lib/hobo/dryml/dryml_support_controller.rb +13 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +6 -7
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +207 -73
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +67 -55
- data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +53 -3
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +75 -107
- data/hobo_files/plugin/lib/hobo/model.rb +236 -429
- data/hobo_files/plugin/lib/hobo/model_controller.rb +277 -437
- data/hobo_files/plugin/lib/hobo/model_router.rb +62 -29
- data/hobo_files/plugin/lib/hobo/rapid_helper.rb +48 -9
- data/hobo_files/plugin/lib/hobo/scopes.rb +98 -0
- data/hobo_files/plugin/lib/hobo/scopes/association_proxy_extensions.rb +31 -0
- data/hobo_files/plugin/lib/hobo/scopes/automatic_scopes.rb +282 -0
- data/hobo_files/plugin/lib/hobo/scopes/defined_scope_proxy_extender.rb +88 -0
- data/hobo_files/plugin/lib/hobo/scopes/scope_reflection.rb +18 -0
- data/hobo_files/plugin/lib/hobo/scopes/scoped_proxy.rb +59 -0
- data/hobo_files/plugin/lib/hobo/undefined.rb +2 -0
- data/hobo_files/plugin/lib/hobo/user.rb +31 -14
- data/hobo_files/plugin/lib/hobo/user_controller.rb +41 -27
- data/hobo_files/plugin/taglibs/core.dryml +9 -11
- data/hobo_files/plugin/taglibs/rapid.dryml +51 -108
- data/hobo_files/plugin/taglibs/rapid_editing.dryml +25 -25
- data/hobo_files/plugin/taglibs/rapid_forms.dryml +111 -79
- data/hobo_files/plugin/taglibs/rapid_generics.dryml +74 -0
- data/hobo_files/plugin/taglibs/rapid_navigation.dryml +23 -21
- data/hobo_files/plugin/taglibs/rapid_pages.dryml +83 -169
- data/hobo_files/plugin/taglibs/rapid_plus.dryml +16 -2
- data/hobo_files/plugin/taglibs/rapid_support.dryml +3 -3
- data/hobo_files/plugin/taglibs/rapid_user_pages.dryml +104 -0
- metadata +60 -55
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +0 -276
- data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +0 -9
- data/hobo_files/plugin/lib/active_record/table_definition.rb +0 -34
- data/hobo_files/plugin/lib/extensions.rb +0 -375
- data/hobo_files/plugin/lib/hobo/email_address.rb +0 -12
- data/hobo_files/plugin/lib/hobo/enum_string.rb +0 -50
- data/hobo_files/plugin/lib/hobo/field_declaration_dsl.rb +0 -43
- data/hobo_files/plugin/lib/hobo/field_spec.rb +0 -68
- data/hobo_files/plugin/lib/hobo/html_string.rb +0 -7
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +0 -40
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +0 -11
- data/hobo_files/plugin/lib/hobo/migrations.rb +0 -12
- data/hobo_files/plugin/lib/hobo/model_queries.rb +0 -117
- data/hobo_files/plugin/lib/hobo/password_string.rb +0 -7
- data/hobo_files/plugin/lib/hobo/percentage.rb +0 -14
- data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +0 -78
- data/hobo_files/plugin/lib/hobo/proc_binding.rb +0 -32
- data/hobo_files/plugin/lib/hobo/text.rb +0 -3
- data/hobo_files/plugin/lib/hobo/textile_string.rb +0 -25
- 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,
|
29
|
-
alias_method_chain :belongs_to, :
|
30
|
-
|
31
|
-
alias_method_chain :
|
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
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
59
|
-
NAME_FIELD_GUESS.detect {|f| f.in? columns
|
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
|
-
@
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
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
|
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
|
99
|
-
|
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
|
-
|
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
|
-
|
125
|
-
|
160
|
+
|
161
|
+
def belongs_to_with_creator_metadata(name, options={}, &block)
|
126
162
|
self.creator_attribute = name.to_sym if options.delete(:creator)
|
127
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
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
|
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
|
-
|
234
|
-
respond_to?(:find_by_id_name)
|
235
|
-
end
|
236
|
-
|
237
|
-
attr_reader :id_name_column
|
193
|
+
public
|
238
194
|
|
239
195
|
|
240
|
-
def
|
241
|
-
|
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
|
203
|
+
if options[:order] == :default
|
275
204
|
options = if default_order.blank?
|
276
|
-
options
|
205
|
+
options.except :order
|
277
206
|
else
|
278
207
|
options.merge(:order => "#{table_name}.#{default_order}")
|
279
208
|
end
|
280
209
|
end
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
227
|
+
cols = columns.*.name
|
334
228
|
SEARCH_COLUMNS_GUESS.select{|c| c.in?(cols) }
|
335
229
|
end
|
336
230
|
|
337
|
-
|
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
|
-
|
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
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
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
|
-
|
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
|
-
|
307
|
+
self.attributes = changes
|
427
308
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
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.
|
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
|
-
|
535
|
-
|
536
|
-
|
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(
|
390
|
+
bta = ActiveRecord::Associations::BelongsToAssociation.new(copy, refl)
|
542
391
|
bta.replace(target)
|
543
|
-
|
392
|
+
copy.instance_variable_set("@#{refl.name}", bta)
|
544
393
|
end
|
545
394
|
end
|
546
|
-
|
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
|
561
|
-
all_cols = self.class.columns
|
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
|
-
|
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)
|
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 <=
|
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
|