hobo 0.9.0 → 0.9.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/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