hobo 0.9.0 → 0.9.100

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/CHANGES.txt +132 -0
  2. data/Rakefile +18 -22
  3. data/bin/hobo +14 -13
  4. data/doctest/scopes.rdoctest +1 -0
  5. data/dryml_generators/rapid/forms.dryml.erb +1 -1
  6. data/dryml_generators/rapid/pages.dryml.erb +24 -24
  7. data/lib/hobo.rb +3 -3
  8. data/lib/hobo/accessible_associations.rb +10 -3
  9. data/lib/hobo/controller.rb +2 -1
  10. data/lib/hobo/dryml.rb +7 -2
  11. data/lib/hobo/dryml/dryml_builder.rb +1 -4
  12. data/lib/hobo/dryml/dryml_generator.rb +5 -3
  13. data/lib/hobo/dryml/taglib.rb +3 -8
  14. data/lib/hobo/dryml/template.rb +3 -10
  15. data/lib/hobo/dryml/template_handler.rb +3 -2
  16. data/lib/hobo/hobo_helper.rb +5 -83
  17. data/lib/hobo/lifecycles.rb +9 -5
  18. data/lib/hobo/lifecycles/actions.rb +5 -5
  19. data/lib/hobo/lifecycles/lifecycle.rb +6 -2
  20. data/lib/hobo/model.rb +14 -36
  21. data/lib/hobo/model_controller.rb +28 -15
  22. data/lib/hobo/model_router.rb +1 -1
  23. data/lib/hobo/permissions.rb +55 -5
  24. data/lib/hobo/permissions/associations.rb +13 -5
  25. data/lib/hobo/rapid_helper.rb +4 -10
  26. data/lib/hobo/scopes/automatic_scopes.rb +9 -1
  27. data/lib/hobo/translations.rb +88 -0
  28. data/lib/hobo/user.rb +1 -2
  29. data/lib/hobo/user_controller.rb +31 -9
  30. data/lib/hobo/view_hints.rb +38 -6
  31. data/rails_generators/hobo_model/templates/hints.rb +4 -1
  32. data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +1 -1
  33. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +2 -1
  34. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +9 -0
  35. data/rails_generators/hobo_rapid/templates/ie7-recalc.js +166 -2
  36. data/rails_generators/hobo_user_model/templates/model.rb +1 -3
  37. data/taglibs/rapid.dryml +2 -1
  38. data/taglibs/rapid_core.dryml +7 -5
  39. data/taglibs/rapid_editing.dryml +14 -10
  40. data/taglibs/rapid_forms.dryml +7 -5
  41. data/taglibs/rapid_generics.dryml +1 -1
  42. data/taglibs/rapid_lifecycles.dryml +3 -2
  43. data/taglibs/rapid_pages.dryml +1 -1
  44. data/taglibs/rapid_support.dryml +1 -1
  45. data/tasks/hobo_tasks.rake +10 -0
  46. metadata +7 -7
  47. data/lib/hobo/bundle.rb +0 -330
@@ -16,7 +16,7 @@ class HoboError < RuntimeError; end
16
16
 
17
17
  module Hobo
18
18
 
19
- VERSION = "0.9.0"
19
+ VERSION = "0.9.100"
20
20
 
21
21
  class PermissionDeniedError < RuntimeError; end
22
22
 
@@ -105,13 +105,13 @@ module Hobo
105
105
  Array(path)
106
106
  end
107
107
 
108
- field, parent = nil
108
+ parent = nil
109
109
  path.each do |field|
110
110
  return nil if object.nil?
111
111
  parent = object
112
112
  object = get_field(parent, field)
113
113
  end
114
- [parent, field, object]
114
+ [parent, path.last, object]
115
115
  end
116
116
 
117
117
 
@@ -42,8 +42,15 @@ module Hobo
42
42
 
43
43
  record = yield id
44
44
  record.attributes = hash
45
- owner.include_in_save(association_name, record) unless owner.new_record? && record.new_record?
46
-
45
+ if owner.new_record? && record.new_record?
46
+ # work around
47
+ # https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/3510-has_many-build-does-not-set-reverse-reflection
48
+ # https://hobo.lighthouseapp.com/projects/8324/tickets/447-validation-problems-with-has_many-accessible-true
49
+ method = "#{owner.class.reverse_reflection(association_name).name}=".to_sym
50
+ record.send(method, owner) if record.respond_to? method
51
+ else
52
+ owner.include_in_save(association_name, record)
53
+ end
47
54
  else
48
55
  # It's already a record
49
56
  record = record_hash_or_string
@@ -87,7 +94,7 @@ module Hobo
87
94
 
88
95
  classy_module(AccessibleAssociations) do
89
96
 
90
- include IncludeInSave
97
+ include Hobo::IncludeInSave
91
98
 
92
99
  # --- has_many mass assignment support --- #
93
100
 
@@ -14,6 +14,7 @@ module Hobo
14
14
 
15
15
  def included_in_class(klass)
16
16
  klass.extend(ClassMethods)
17
+ klass.send(:include, Hobo::Translations)
17
18
  klass.class_eval do
18
19
  before_filter :login_from_cookie
19
20
  alias_method_chain :redirect_to, :object_url
@@ -157,7 +158,7 @@ module Hobo
157
158
  end
158
159
 
159
160
  def not_found(error)
160
- if "not_found_response".in?(self.class.superclass.instance_methods)
161
+ if self.class.superclass.method_defined?("not_found_response")
161
162
  super
162
163
  elsif render_tag("not-found-page", {}, :status => 404)
163
164
  # cool
@@ -24,9 +24,9 @@ module Hobo
24
24
  CORE_TAGLIB = { :src => "core", :plugin => "hobo" }
25
25
 
26
26
  DEFAULT_IMPORTS = (if defined?(ApplicationHelper)
27
- [Hobo::HoboHelper, ApplicationHelper]
27
+ [Hobo::HoboHelper, Hobo::Translations, ApplicationHelper]
28
28
  else
29
- [Hobo::HoboHelper]
29
+ [Hobo::HoboHelper, Hobo::Translations]
30
30
  end)
31
31
 
32
32
  @renderer_classes = {}
@@ -38,6 +38,11 @@ module Hobo
38
38
 
39
39
  def enable
40
40
  ActionView::Template.register_template_handler("dryml", Hobo::Dryml::TemplateHandler)
41
+ if ActionView::Template.respond_to? :exempt_from_layout
42
+ ActionView::Template.exempt_from_layout('dryml')
43
+ elsif
44
+ ActionView::Base.exempt_from_layout('dryml')
45
+ end
41
46
  DrymlGenerator.enable
42
47
  end
43
48
 
@@ -61,7 +61,7 @@ module Hobo::Dryml
61
61
  def erb_process(erb_src, method_def=false)
62
62
  trim_mode = ActionView::TemplateHandlers::ERB.erb_trim_mode
63
63
  erb = ERB.new(erb_src, nil, trim_mode, "output_buffer")
64
- src = erb.src[("output_buffer = '';").length..-("; output_buffer".length)]
64
+ src = erb.src.split(';')[1..-2].join(';')
65
65
 
66
66
  if method_def
67
67
  src.sub /^\s*def.*?\(.*?\)/, '\0 __in_erb_template=true; '
@@ -118,9 +118,6 @@ module Hobo::Dryml
118
118
  template_dir = File.dirname(template_path)
119
119
  options = options.merge(:template_dir => template_dir)
120
120
 
121
- # Pass on the current bundle, if there is one, to the sub-taglib
122
- options[:bundle] = template.bundle.name unless template.bundle.nil? || options[:bundle] || options[:plugin]
123
-
124
121
  taglib = Taglib.get(options)
125
122
  taglib.import_into(@environment, options[:as])
126
123
  end
@@ -75,6 +75,9 @@ module Hobo
75
75
 
76
76
 
77
77
  def run
78
+ # Ensure all view hints loaded before running
79
+ Hobo::Model.all_models.*.view_hints
80
+
78
81
  [nil, *Hobo.subsites].each { |s| run_for_subsite(s) }
79
82
  end
80
83
 
@@ -156,11 +159,10 @@ module Hobo
156
159
 
157
160
 
158
161
  def model_name(*options)
159
- name = model.view_hints.model_name
160
- name = name.pluralize if :plural.in?(options)
161
- name = name.titleize if :title.in?(options)
162
+ name = :plural.in?(options) ? model.view_hints.model_name_plural : model.view_hints.model_name
162
163
  name = name.titleize.downcase if :lowercase.in?(options)
163
164
  name = name.underscore.gsub('_', '-').gsub('/', '--') if :dashed.in?(options)
165
+ name = name.camelize if :camel.in?(options)
164
166
  name
165
167
  end
166
168
 
@@ -14,8 +14,7 @@ module Hobo
14
14
  if taglib
15
15
  taglib.reload
16
16
  else
17
- bundle = options[:bundle] && Bundle.bundles[options[:bundle]]
18
- taglib = Taglib.new(src_file, bundle)
17
+ taglib = Taglib.new(src_file)
19
18
  @cache[src_file] = taglib
20
19
  end
21
20
  taglib
@@ -33,9 +32,6 @@ module Hobo
33
32
  "#{HOBO_ROOT}/taglibs"
34
33
  elsif plugin
35
34
  "#{RAILS_ROOT}/vendor/plugins/#{plugin}/taglibs"
36
- elsif (bundle_name = options[:bundle])
37
- bundle = Bundle.bundles[bundle_name] or raise ArgumentError, "No such bundle: #{options[:bundle]}"
38
- "#{RAILS_ROOT}/vendor/plugins/#{bundle.plugin}/taglibs"
39
35
  elsif options[:src] =~ /\//
40
36
  "#{RAILS_ROOT}/app/views"
41
37
  elsif options[:template_dir] =~ /^#{HOBO_ROOT}/
@@ -52,9 +48,8 @@ module Hobo
52
48
 
53
49
  end
54
50
 
55
- def initialize(src_file, bundle)
51
+ def initialize(src_file)
56
52
  @src_file = src_file
57
- @bundle = bundle
58
53
  load
59
54
  end
60
55
 
@@ -92,7 +87,7 @@ module Hobo
92
87
  end
93
88
 
94
89
  end
95
- template = Template.new(File.read(@src_file), @module, @src_file, @bundle)
90
+ template = Template.new(File.read(@src_file), @module, @src_file)
96
91
  template.compile([], [])
97
92
  @last_load_time = File.mtime(@src_file)
98
93
  end
@@ -33,11 +33,10 @@ module Hobo::Dryml
33
33
  end
34
34
  end
35
35
 
36
- def initialize(src, environment, template_path, bundle=nil)
36
+ def initialize(src, environment, template_path)
37
37
  @src = src
38
38
  @environment = environment # a class or a module
39
39
  @template_path = template_path.sub(%r(^#{Regexp.escape(RAILS_ROOT)}/), "")
40
- @bundle = bundle
41
40
 
42
41
  @builder = Template.build_cache[@template_path] || DRYMLBuilder.new(self)
43
42
  @builder.set_environment(environment)
@@ -45,7 +44,7 @@ module Hobo::Dryml
45
44
  @last_element = nil
46
45
  end
47
46
 
48
- attr_reader :tags, :template_path, :bundle
47
+ attr_reader :tags, :template_path
49
48
 
50
49
  def compile(local_names=[], auto_taglibs=[])
51
50
  now = Time.now
@@ -178,7 +177,7 @@ module Hobo::Dryml
178
177
  require_toplevel(el)
179
178
  require_attribute(el, "as", /^#{DRYML_NAME}$/, true)
180
179
  options = {}
181
- %w(src module plugin bundle as).each do |attr|
180
+ %w(src module plugin as).each do |attr|
182
181
  options[attr.to_sym] = el.attributes[attr] if el.attributes[attr]
183
182
  end
184
183
  @builder.add_build_instruction(:include, options)
@@ -261,8 +260,6 @@ module Hobo::Dryml
261
260
  klass = HoboFields.to_class(for_type) or
262
261
  dryml_exception("No such type in polymorphic tag definition: '#{for_type}'", el)
263
262
  klass.name
264
- elsif for_type =~ /^_.*_$/
265
- rename_class(for_type)
266
263
  else
267
264
  for_type
268
265
  end.underscore.gsub('/', '__')
@@ -994,10 +991,6 @@ module Hobo::Dryml
994
991
  "#{name}_#{@gensym_counter}"
995
992
  end
996
993
 
997
- def rename_class(name)
998
- @bundle && name.starts_with?("_") ? @bundle.send(name) : name
999
- end
1000
-
1001
994
  def include_source_metadata
1002
995
  # disabled for now -- we're still getting broken rendering with this feature on
1003
996
  return false
@@ -17,7 +17,8 @@ module Hobo::Dryml
17
17
 
18
18
  def render_for_rails22(template, view, local_assigns)
19
19
  renderer = Hobo::Dryml.page_renderer_for_template(view, local_assigns.keys, template)
20
- this = @view.instance_variable_set("@this", view.controller.send(:dryml_context) || local_assigns[:this])
20
+ this = view.controller.send(:dryml_context) || local_assigns[:this]
21
+ @view._?.instance_variable_set("@this", this)
21
22
  s = renderer.render_page(this, local_assigns)
22
23
 
23
24
  # Important to strip whitespace, or the browser hangs around for ages (FF2)
@@ -177,7 +178,7 @@ module ActionView
177
178
  end
178
179
  end
179
180
 
180
- if instance_methods.include? "find_template"
181
+ if method_defined? "find_template"
181
182
  # only rails 2.3 has this function
182
183
  alias_method_chain :find_template, :dryml
183
184
  end
@@ -143,7 +143,7 @@ module Hobo
143
143
 
144
144
  def type_id(type=nil)
145
145
  type ||= (this.is_a?(Class) && this) || this_type || this.class
146
- HoboFields.to_name(type) || type.name.underscore.gsub("/", "__")
146
+ HoboFields.to_name(type) || type.name.to_s.underscore.gsub("/", "__")
147
147
  end
148
148
 
149
149
 
@@ -485,15 +485,13 @@ module Hobo
485
485
  # --- ViewHint Helpers --- #
486
486
 
487
487
  def this_field_name
488
- key = "#{this_parent.class.try.name.underscore}.#{this_field.to_s}"
489
- default = this_parent.class.try.view_hints.try.field_name(this_field) || this_field
490
- I18n.t(key, :default => default, :scope => [:activerecord, :attributes], :count => 1)
488
+ this_parent.class.try.view_hints.try.field_name(this_field) || this_field
491
489
  end
492
490
 
493
491
  def this_field_help
494
- key = "#{this_parent.class.try.name.pluralize.underscore}.hints.#{this_field.to_s}"
492
+ key = "#{this_parent.class.try.name.tableize}.hints.#{this_field.to_s}"
495
493
  default = this_parent.class.try.view_hints.try.field_help[this_field.to_sym] || ""
496
- I18n.t(key,:default=>default)
494
+ Hobo::Translations.ht(key, :default=>default)
497
495
  end
498
496
 
499
497
 
@@ -510,83 +508,7 @@ module Hobo
510
508
  logger.debug("DRYML THIS = #{this.typed_id rescue this.inspect}")
511
509
  logger.debug("###################\n")
512
510
  args.first unless args.empty?
513
- end
514
-
515
- # --- Translation Helper --- #
516
- #
517
- # Uses RoR native I18n.translate.
518
- #
519
- # Adds some conventions for easier hobo translation.
520
- # 1. Assumes the first part of the key to be a model name (e.g.: users.index.title -> user)
521
- # 2. Tries to translate the model by lookup for: (e.g.: user-> activerecord.models.user)
522
- # 3. Adds a default fallback to the beginning of the fallback chain
523
- # by replacing the first part of the key with "hobo" and using the translated model name
524
- # as additional attribute. This allows us to have default translations
525
- # (e.g.: hobo.index.title: "{{model}} Index")
526
- #
527
- # Is also used as a tag in the dryml-view files. The syntax is:
528
- # <ht key="my.app">My Application</ht>
529
- # --> Will lookup the "my.app"-key for your locale and replaces the "My Application" content
530
- # if found.
531
- #
532
- # <ht key="my" app="Program">My Application</ht>
533
- # --> Will look up both the "my"- and "app"-key for your locale, and replaces the
534
- # "My Application" with the "my"-key contents (interpolated using the "app"-key.
535
- # sample.en.yml-file:
536
- # "no":
537
- # my: "Mitt {{app}}"
538
- # The output should be: Mitt Program
539
- #
540
- # Otherwise with features as the ht method, step 1, 2 and 3 above.
541
- def ht(key, options={})
542
-
543
- # Check if called as a tag, i.e. like this <ht></ht>
544
- if (key.class == Hash)
545
- if key.has_key?(:default) && !key[:default].blank?
546
- logger.warn "hobo-i18n: 'default' should not be used as an attribute on the ht-tag. If used, then you need to make sure that the tags inner-contents are not used. These are normally treated as defaults automatically, but if there is a default attribute then that inner-content will be hidden from this method - and will not be replaced with the translation found."
547
- end
548
- defaults = options[:default];
549
- # Swap key and options, remove options[:key]
550
- options = key
551
- key = options.delete(:key) # returns value for options[:key] as well as deleting it
552
- # Set options[:default] to complete the tag-argument-conversion process.
553
- options[:default] = (defaults.class == Proc) ? [defaults.call(options)] : (options[:default].blank? ? [] : [options[:default]])
554
- else
555
- # Not called as a tag. Prepare options[:default].
556
- if options[:default].blank?
557
- options[:default]=[]
558
- elsif options[:default].class != Array
559
- options[:default] = [options[:default]]
560
- end
561
- end
562
-
563
- # assume the first part of the key to be the model
564
- keys = key.to_s.split(".")
565
- if keys.length > 1
566
- model = keys.shift()
567
- subkey = keys.join(".")
568
- else
569
- subkey = key
570
- end
571
-
572
- # add :"hobo.#{key}" as the first fallback
573
- options[:default].unshift("hobo.#{subkey}".to_sym)
574
-
575
- # translate the model
576
- unless model.blank?
577
- translated_model = I18n.translate( "activerecord.models.#{model.singularize.underscore}", :default=>model).titleize
578
- options[:model] = translated_model
579
- end
580
-
581
- begin
582
- key_prefix = "<span class='translation-key'>#{key}</span>" if HOBO_SHOW_LOCALE_KEYS
583
- rescue
584
- puts "Set HOBO_SHOW_LOCALE_KEYS=true in your environment to get all locale keys displayed"
585
- end
586
-
587
- logger.info "..translate(#{key}, #{options.inspect}) to #{I18n.locale}"
588
- I18n.translate(key.to_sym, options)+(key_prefix ? key_prefix:"")
589
- end
511
+ end
590
512
 
591
513
  end
592
514
 
@@ -30,13 +30,17 @@ module Hobo
30
30
  module_eval "class ::#{name}::Lifecycle < Hobo::Lifecycles::Lifecycle; end"
31
31
  lifecycle = self::Lifecycle
32
32
  lifecycle.init(self, options)
33
+
34
+ module_eval "class ::#{name}::LifecycleStateField < HoboFields::LifecycleState; end"
35
+ state_field_class = self::LifecycleStateField
36
+ state_field_class.table_name = name.tableize
33
37
  end
34
38
 
35
- dsl = DeclarationDSL.new(lifecycle)
39
+ dsl = Hobo::Lifecycles::DeclarationDSL.new(lifecycle)
36
40
  dsl.instance_eval(&block)
37
41
 
38
42
  default = lifecycle.default_state ? { :default => lifecycle.default_state.name.to_s } : {}
39
- declare_field(options[:state_field], :string, default)
43
+ declare_field(options[:state_field], state_field_class, default)
40
44
  unless options[:index] == false
41
45
  index_options = { :name => options[:index] } unless options[:index] == true
42
46
  index(options[:state_field], index_options || {})
@@ -82,15 +86,15 @@ module Hobo
82
86
 
83
87
 
84
88
  def lifecycle
85
- @lifecycle ||= if defined? self.class::Lifecycle
89
+ @lifecycle ||= if self.class.const_defined?(:Lifecycle)
86
90
  self.class::Lifecycle.new(self)
87
91
  else
88
92
  # search through superclasses
89
93
  current = self.class.superclass
90
- until (defined?(current::Lifecycle) || current.nil? || !current.respond_to?(:lifecycle)) do
94
+ until (current.const_defined?(:Lifecycle) || current.nil? || !current.respond_to?(:lifecycle)) do
91
95
  current = current.superclass
92
96
  end
93
- current.class::Lifecycle.new(self) if defined? current::Lifecycle
97
+ current::Lifecycle.new(self) if current.const_defined?(:Lifecycle)
94
98
  end
95
99
  end
96
100
 
@@ -15,11 +15,8 @@ module Hobo
15
15
  return true if available_to.nil? # available_to not specified (these steps are not published)
16
16
  acting_user_is?(available_to, record)
17
17
  end
18
-
19
-
20
- def acting_user_is?(who, record)
21
- user = record.acting_user
22
18
 
19
+ def publishable_by(user, who, record)
23
20
  case who
24
21
  when :all
25
22
  true
@@ -32,7 +29,7 @@ module Hobo
32
29
 
33
30
  when Array
34
31
  # recursively apply the same test to every item in the array
35
- who.detect { |w| acting_user_is?(w, record) }
32
+ who.detect { |w| publishable_by(user, w, record) }
36
33
 
37
34
  else
38
35
  refl = record.class.reflections[who]
@@ -53,6 +50,9 @@ module Hobo
53
50
  end
54
51
  end
55
52
 
53
+ def acting_user_is?(who, record)
54
+ publishable_by(record.acting_user, who, record)
55
+ end
56
56
 
57
57
  def run_hook(record, hook, *args)
58
58
  case hook
@@ -159,7 +159,7 @@ module Hobo
159
159
  state ? state.transitions_out : []
160
160
  end
161
161
 
162
-
162
+ # see also publishable_transitions_for
163
163
  def available_transitions_for(user, name=nil)
164
164
  name = name.to_sym if name
165
165
  matches = available_transitions
@@ -169,6 +169,10 @@ module Hobo
169
169
  end
170
170
  end
171
171
 
172
+ def publishable_transitions_for(user)
173
+ available_transitions_for(user).select {|t| t.publishable_by(user, t.available_to, record)}
174
+ end
175
+
172
176
 
173
177
  def become(state_name, validate=true)
174
178
  state_name = state_name.to_sym
@@ -180,7 +184,7 @@ module Hobo
180
184
  else
181
185
  s = self.class.states[state_name]
182
186
  raise ArgumentError, "No such state '#{state_name}' for #{record.class.name}" unless s
183
-
187
+
184
188
  if record.save(validate)
185
189
  s.activate! record
186
190
  self.active_step = nil # That's the end of this step