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.
- 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
|