Class: ActivePresenter::Base

Inherits:
Object
  • Object
show all
Extended by:
ActiveModel::Callbacks, ActiveModel::Naming, ActiveModel::Translation
Includes:
ActiveModel::Conversion, ActiveModel::MassAssignmentSecurity
Defined in:
lib/active_presenter/base.rb

Overview

Base class for presenters. See README for usage.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args = {}) ⇒ Base

Accepts arguments in two forms. For example, if you had a SignupPresenter that presented User, and Account, you could specify arguments in the following two forms:

1. SignupPresenter.new(:user_login => 'james', :user_password => 'swordfish', :user_password_confirmation => 'swordfish', :account_subdomain => 'giraffesoft')
  - This form is useful for initializing a new presenter from the params hash: i.e. SignupPresenter.new(params[:signup_presenter])
2. SignupPresenter.new(:user => User.find(1), :account => Account.find(2))
  - This form is useful if you have instances that you'd like to edit using the presenter. You can subsequently call presenter.update_attributes(params[:signup_presenter]) just like with a regular AR instance.

Both forms can also be mixed together: SignupPresenter.new(:user => User.find(1), :user_login => ‘james’)

In this case, the login attribute will be updated on the user instance provided.

If you don’t specify an instance, one will be created by calling Model.new



92
93
94
95
96
97
98
99
100
# File 'lib/active_presenter/base.rb', line 92

def initialize(args = {})
  @errors = ActiveModel::Errors.new(self)
  return self unless args
  presented.each do |type, klass|
    value = args.delete(type)
    send("#{type}=", value.is_a?(klass) ? value : klass.new)
  end
  self.attributes = args
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Handles the decision about whether to delegate getters and setters to presentable instances.



135
136
137
# File 'lib/active_presenter/base.rb', line 135

def method_missing(method_name, *args, &block)
  presented_attribute?(method_name) ? delegate_message(method_name, *args, &block) : super
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



12
13
14
# File 'lib/active_presenter/base.rb', line 12

def errors
  @errors
end

Class Method Details

.human_attribute_name(attribute_key_name, options = {}) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/active_presenter/base.rb', line 48

def human_attribute_name(attribute_key_name, options = {})
  presentable_type = presented.keys.detect do |type|
    attribute_key_name.to_s.starts_with?("#{type}_") || attribute_key_name.to_s == type.to_s
  end
  attribute_key_name_without_class = attribute_key_name.to_s.gsub("#{presentable_type}_", "")
  
  if presented[presentable_type] and attribute_key_name_without_class != presentable_type.to_s
    presented[presentable_type].human_attribute_name(attribute_key_name_without_class, options)
  else
    I18n.translate(presentable_type, options.merge(:default => presentable_type.to_s.humanize, :scope => [:activerecord, :models]))
  end
end

.human_name(options = {}) ⇒ Object

:nodoc:



71
72
73
74
75
76
77
# File 'lib/active_presenter/base.rb', line 71

def human_name(options = {}) # :nodoc:
  defaults = self_and_descendants_from_active_record.map do |klass|
    :"#{klass.name.underscore}"
  end 
  defaults << self.name.humanize
  I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
end

.presents(*types) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/active_presenter/base.rb', line 33

def presents(*types)
  types_and_classes = types.extract_options!
  types.each { |t| types_and_classes[t] = t.to_s.tableize.classify.constantize }

  attr_accessor *types_and_classes.keys
  
  types_and_classes.keys.each do |t|
    define_method("#{t}_errors") do
      send(t).errors
    end
    
    presented[t] = types_and_classes[t]
  end
end

.self_and_descendants_from_active_recordObject

Since ActivePresenter does not descend from ActiveRecord, we need to mimic some ActiveRecord behavior in order for the ActiveRecord::Errors object we’re using to work properly.

This problem was introduced with Rails 2.3.4. Fix courtesy gist.github.com/191263



67
68
69
# File 'lib/active_presenter/base.rb', line 67

def self_and_descendants_from_active_record # :nodoc:
  [self]
end

Instance Method Details

#attributes=(attrs) ⇒ Object

Set the attributes of the presentable instances using the type_attribute form (i.e. user_login => ‘james’), or the multiparameter attribute form (i.e. => “1980”, user_birthday(2i) => “3”)



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/active_presenter/base.rb', line 106

def attributes=(attrs)
  return if attrs.nil?

  attrs = attrs.stringify_keys      
  multi_parameter_attributes = {}
  
  sanitize_for_mass_assignment(attrs).each do |k,v|
    if (base_attribute = k.to_s.split("(").first) != k.to_s
      presentable = presentable_for(base_attribute)
      multi_parameter_attributes[presentable] ||= {}
      multi_parameter_attributes[presentable].merge!(flatten_attribute_name(k,presentable).to_sym => v)
    else
      send("#{k}=", v) unless attribute_protected?(k)
    end
  end
  
  multi_parameter_attributes.each do |presentable,multi_attrs|
    send(presentable).send(:attributes=, multi_attrs)
  end
end

#changed?Boolean

Do any of the attributes have unsaved changes?

Returns:

  • (Boolean)


156
157
158
# File 'lib/active_presenter/base.rb', line 156

def changed?
  presented_instances.map(&:changed?).any?
end

#idObject

We define #id and #new_record? to play nice with form_for(@presenter) in Rails



219
220
221
# File 'lib/active_presenter/base.rb', line 219

def id # :nodoc:
  nil
end

#new_record?Boolean

Returns:

  • (Boolean)


223
224
225
# File 'lib/active_presenter/base.rb', line 223

def new_record?
  presented_instances.map(&:new_record?).all?
end

#persisted?Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/active_presenter/base.rb', line 227

def persisted?
  presented_instances.map(&:persisted?).all?
end

#respond_to?(method, include_private = false) ⇒ Boolean

Makes sure that the presenter is accurate about responding to presentable’s attributes, even though they are handled by method_missing.

Returns:

  • (Boolean)


129
130
131
# File 'lib/active_presenter/base.rb', line 129

def respond_to?(method, include_private = false)
  presented_attribute?(method) || super
end

#save(options = {}) ⇒ Object

Save all of the presentables, wrapped in a transaction.

Returns true or false based on success.



164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/active_presenter/base.rb', line 164

def save(options={})
  saved = false
  ActiveRecord::Base.transaction do
    if !perform_validations?(options) || (perform_validations?(options) && valid?)
      _run_save_callbacks do
        saved = presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save}
        raise ActiveRecord::Rollback unless saved
      end
    end
  end
  saved
end

#save!(options = {}) ⇒ Object

Save all of the presentables wrapped in a transaction.

Returns true on success, will raise otherwise.



181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/active_presenter/base.rb', line 181

def save!(options={})
  saved = false
  ActiveRecord::Base.transaction do
    raise ActiveRecord::RecordInvalid.new(self) if perform_validations?(options) && !valid?
    _run_save_callbacks do
      presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save!}
      saved = true
    end
  raise ActiveRecord::RecordNotSaved.new(self) unless saved
  end
  saved
end

#save?(presented_key, presented_instance) ⇒ Boolean

Should this presented instance be saved? By default, this returns true Called from #save and #save!

For

class SignupPresenter < ActivePresenter::Base
  presents :account, :user
end

#save? will be called twice:

save?(:account, #<Account:0x1234dead>)
save?(:user, #<User:0xdeadbeef>)

Returns:

  • (Boolean)


214
215
216
# File 'lib/active_presenter/base.rb', line 214

def save?(presented_key, presented_instance)
  true
end

#update_attributes(attrs) ⇒ Object

Update attributes, and save the presentables

Returns true or false based on success.



198
199
200
201
# File 'lib/active_presenter/base.rb', line 198

def update_attributes(attrs)
  self.attributes = attrs
  save
end

#valid?Boolean

Returns boolean based on the validity of the presentables by calling valid? on each of them.

Returns:

  • (Boolean)


141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/active_presenter/base.rb', line 141

def valid?
  validated = false
  errors.clear
  result = _run_validation_callbacks do
    presented.each do |type, klass|
      presented_inst = (send(type) || klass.new)
      next unless save?(type, presented_inst)
      merge_errors(presented_inst, type) unless presented_inst.valid?
    end
    validated = true
  end
  errors.empty? && validated
end