Class: ActivePresenter::Base
- Inherits:
-
Object
- Object
- ActivePresenter::Base
- Includes:
- ActiveSupport::Callbacks
- Defined in:
- lib/active_presenter/base.rb
Overview
Base class for presenters. See README for usage.
Class Method Summary collapse
-
.accessible_attributes ⇒ Object
Returns an array of all the attributes that have been made accessible to mass-assignment.
-
.attr_accessible(*attributes) ⇒ Object
Note that
attr_accessible
is still applied to the received hash. -
.attr_protected(*attributes) ⇒ Object
Note that
attr_protected
is still applied to the received hash. - .human_attribute_name(attribute_key_name, options = {}) ⇒ Object
-
.human_name(options = {}) ⇒ Object
:nodoc:.
-
.presents(*types) ⇒ Object
Indicates which models are to be presented by this presenter.
-
.protected_attributes ⇒ Object
Returns an array of all the attributes that have been protected from mass-assignment.
-
.self_and_descendants_from_active_record ⇒ Object
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.
Instance Method Summary collapse
-
#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”).
-
#changed? ⇒ Boolean
Do any of the attributes have unsaved changes?.
-
#errors ⇒ Object
Returns an instance of ActiveRecord::Errors with all the errors from the presentables merged in using the type_attribute form (i.e. user_login).
-
#id ⇒ Object
We define #id and #new_record? to play nice with form_for(@presenter) in Rails.
-
#initialize(args = {}) ⇒ Base
constructor
Accepts arguments in two forms.
-
#method_missing(method_name, *args, &block) ⇒ Object
Handles the decision about whether to delegate getters and setters to presentable instances.
- #new_record? ⇒ Boolean
-
#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.
-
#save ⇒ Object
Save all of the presentables, wrapped in a transaction.
-
#save! ⇒ Object
Save all of the presentables wrapped in a transaction.
-
#save?(presented_key, presented_instance) ⇒ Boolean
Should this presented instance be saved? By default, this returns true Called from #save and #save!.
-
#update_attributes(attrs) ⇒ Object
Update attributes, and save the presentables.
-
#valid? ⇒ Boolean
Returns boolean based on the validity of the presentables by calling valid? on each of them.
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
107 108 109 110 111 112 113 114 115 116 |
# File 'lib/active_presenter/base.rb', line 107 def initialize(args = {}) 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.
152 153 154 |
# File 'lib/active_presenter/base.rb', line 152 def method_missing(method_name, *args, &block) presented_attribute?(method_name) ? (method_name, *args, &block) : super end |
Class Method Details
.accessible_attributes ⇒ Object
Returns an array of all the attributes that have been made accessible to mass-assignment.
91 92 93 |
# File 'lib/active_presenter/base.rb', line 91 def self.accessible_attributes # :nodoc: read_inheritable_attribute(:attr_accessible) end |
.attr_accessible(*attributes) ⇒ Object
Note that attr_accessible
is still applied to the received hash. Thus, with this technique you can at most narrow the list of accessible attributes for a particular mass-assignment call.
86 87 88 |
# File 'lib/active_presenter/base.rb', line 86 def self.attr_accessible(*attributes) write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || [])) end |
.attr_protected(*attributes) ⇒ Object
Note that attr_protected
is still applied to the received hash. Thus, with this technique you can at most extend the list of protected attributes for a particular mass-assignment call.
74 75 76 |
# File 'lib/active_presenter/base.rb', line 74 def self.attr_protected(*attributes) write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || [])) end |
.human_attribute_name(attribute_key_name, options = {}) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/active_presenter/base.rb', line 40 def self.human_attribute_name(attribute_key_name, = {}) 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, ) else I18n.translate(presentable_type, .merge(:default => presentable_type.to_s.humanize, :scope => [:activerecord, :models])) end end |
.human_name(options = {}) ⇒ Object
:nodoc:
63 64 65 66 67 68 69 |
# File 'lib/active_presenter/base.rb', line 63 def self.human_name( = {}) # :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()) 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
25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/active_presenter/base.rb', line 25 def self.presents(*types) types_and_classes = types. 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 |
.protected_attributes ⇒ Object
Returns an array of all the attributes that have been protected from mass-assignment.
79 80 81 |
# File 'lib/active_presenter/base.rb', line 79 def self.protected_attributes # :nodoc: read_inheritable_attribute(:attr_protected) end |
.self_and_descendants_from_active_record ⇒ Object
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
59 60 61 |
# File 'lib/active_presenter/base.rb', line 59 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”)
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/active_presenter/base.rb', line 122 def attributes=(attrs) return if attrs.nil? attrs = attrs.stringify_keys multi_parameter_attributes = {} attrs = remove_attributes_protected_from_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?
179 180 181 |
# File 'lib/active_presenter/base.rb', line 179 def changed? presented_instances.map(&:changed?).any? end |
#errors ⇒ Object
Returns an instance of ActiveRecord::Errors with all the errors from the presentables merged in using the type_attribute form (i.e. user_login).
158 159 160 |
# File 'lib/active_presenter/base.rb', line 158 def errors @errors ||= ActiveRecord::Errors.new(self) end |
#id ⇒ Object
We define #id and #new_record? to play nice with form_for(@presenter) in Rails
244 245 246 |
# File 'lib/active_presenter/base.rb', line 244 def id # :nodoc: nil end |
#new_record? ⇒ Boolean
248 249 250 |
# File 'lib/active_presenter/base.rb', line 248 def new_record? true 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.
146 147 148 |
# File 'lib/active_presenter/base.rb', line 146 def respond_to?(method, include_private = false) presented_attribute?(method) || super end |
#save ⇒ Object
Save all of the presentables, wrapped in a transaction.
Returns true or false based on success.
187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/active_presenter/base.rb', line 187 def save saved = false ActiveRecord::Base.transaction do if valid? && run_callbacks_with_halt(:before_save) saved = presented.keys.select {|key| save?(key, send(key))}.all? {|key| send(key).save} raise ActiveRecord::Rollback unless saved # TODO: Does this happen implicitly? end run_callbacks_with_halt(:after_save) if saved end saved end |
#save! ⇒ Object
Save all of the presentables wrapped in a transaction.
Returns true on success, will raise otherwise.
206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/active_presenter/base.rb', line 206 def save! raise ActiveRecord::RecordInvalid.new(self) unless valid? raise ActiveRecord::RecordNotSaved unless run_callbacks_with_halt(:before_save) ActiveRecord::Base.transaction do presented.keys.select {|key| save?(key, send(key))}.each {|key| send(key).save!} run_callbacks_with_halt(:after_save) end true 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>)
239 240 241 |
# File 'lib/active_presenter/base.rb', line 239 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.
223 224 225 226 |
# File 'lib/active_presenter/base.rb', line 223 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.
164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/active_presenter/base.rb', line 164 def valid? errors.clear if run_callbacks_with_halt(:before_validation) 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 errors.empty? end end |