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

[View source]

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

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.

[View source]

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

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.


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

def errors
  @errors
end

Class Method Details

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

[View source]

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

def self.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:

[View source]

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

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

Indicates which models are to be presented by this presenter. i.e.

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

In the above example, :user will (predictably) become User. If you want to override this behaviour, specify the desired types in a hash, as so:

class PresenterWithTwoAddresses < ActivePresenter::Base
  presents :primary_address => Address, :secondary_address => Address
end
[View source]

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

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

    # We must reassign in derrived classes rather than mutating the attribute in Base
    self.presented = self.presented.merge(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

[View source]

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

def self.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”)

[View source]

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

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

  attrs = attrs.stringify_keys      
  multi_parameter_attributes = {}
  attrs = sanitize_for_mass_assignment(attrs)

  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)
[View source]

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

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

#idObject

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

[View source]

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

def id # :nodoc:
  nil
end

#new_record?Boolean

Returns:

  • (Boolean)
[View source]

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

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

#persisted?Boolean

Returns:

  • (Boolean)
[View source]

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

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)
[View source]

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

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

#saveObject

Save all of the presentables, wrapped in a transaction.

Returns true or false based on success.

[View source]

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

def save
  saved = false
  ActiveRecord::Base.transaction do
    if 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!Object

Save all of the presentables wrapped in a transaction.

Returns true on success, will raise otherwise.

[View source]

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

def save!
  saved = false
  ActiveRecord::Base.transaction do
    raise ActiveRecord::RecordInvalid.new(self) unless 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)
[View source]

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

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.

[View source]

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

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)
[View source]

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

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