Class: Alchemy::Resource
- Inherits:
-
Object
- Object
- Alchemy::Resource
- Defined in:
- lib/alchemy/resource.rb
Overview
Alchemy::Resource
Used to DRY up resource like structures in Alchemy’s admin backend. So far Language, User and Tag already uses this.
It provides convenience methods to create an admin interface without further knowledge about the model and the controller (it’s instantiated with controller_path at least and guesses the model accordingly)
For examples how to use in controllers see Alchemy::ResourcesController or inherit from it directly.
Naming Conventions
As Rails’ form helpers, path helpers, etc. and declarative authorization rely on controller_path even if the model class is named differently (or sits in another namespace) model and controller are handled separatly here. Therefore “resource” always refers to the controller_path whereas “model” refers to the model class.
Skip attributes
Usually you don’t want your users to see and edit all attributes provided by a model. Hence some default attributes, namely id, updated_at, created_at, creator_id and updater_id are not returned by Resource#attributes.
If you want to skip a different set of attributes just define a skipped_alchemy_resource_attributes
class method in your model class that returns an array of strings.
Example
def self.skipped_alchemy_resource_attributes
%w(id updated_at secret_token remote_ip)
end
Restrict attributes
Beside skipping certain attributes you can also restrict them. Restricted attributes can not be edited by the user but still be seen in the index view. No attributes are restricted by default.
Example
def self.restricted_alchemy_resource_attributes
%w(synced_at remote_record_id)
end
Resource relations
Alchemy::Resource can take care of ActiveRecord relations.
BelongsTo Relations
For belongs_to associations you will have to define a alchemy_resource_relations
class method in your model class:
def self.alchemy_resource_relations
{
location: {attr_method: 'name', attr_type: 'string'},
organizer: {attr_method: 'name', attr_type: 'string'}
}
end
With this knowledge Resource#attributes will return location#name and organizer#name instead of location_id and organizer_id. Refer to Alchemy::ResourcesController for further details on usage.
Creation
Resource needs a controller_path at least. Without other arguments it will guess the model name from it and assume that the model doesn’t live in an engine. Moreover model and controller has to follow Rails’ naming convention:
Event -> EventsController
It will also strip “admin” automatically, so this is also valid:
Event -> Admin::EventsController
If your Resource and it’s controllers are part of an engine you need to provide Alchemy’s module_definition, so resource can provide the correct url_proxy. If you don’t declare it in Alchemy, you need at least provide the following hash (i.e. if your engine is named EventEngine):
resource = Resource.new(controller_path, {"engine_name" => "event_engine"})
If you don’t want to stick with these conventions you can separate model and controller by providing a model class (for example used by Alchemy’s Tags admin interface):
resource = Resource.new('/admin/tags', {"engine_name"=>"alchemy"}, ActsAsTaggableOn::Tag)
Constant Summary collapse
- DEFAULT_SKIPPED_ATTRIBUTES =
%w(id updated_at created_at creator_id updater_id)
- DEFAULT_SKIPPED_ASSOCIATIONS =
%w(creator updater)
Instance Attribute Summary collapse
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#model_associations ⇒ Object
Returns the value of attribute model_associations.
-
#resource_relations ⇒ Object
Returns the value of attribute resource_relations.
-
#restricted_attributes ⇒ Object
Returns the value of attribute restricted_attributes.
-
#skipped_attributes ⇒ Object
Returns the value of attribute skipped_attributes.
Instance Method Summary collapse
- #attributes ⇒ Object
- #editable_attributes ⇒ Object
- #engine_name ⇒ Object
-
#help_text_for(attribute) ⇒ Object
Returns a help text for resource’s form.
- #in_engine? ⇒ Boolean
-
#initialize(controller_path, module_definition = nil, custom_model = nil) ⇒ Resource
constructor
A new instance of Resource.
-
#model_association_names ⇒ Object
Returns an array of underscored association names.
- #namespace_for_scope ⇒ Object
- #namespaced_resource_name ⇒ Object
- #resource_array ⇒ Object
- #resource_name ⇒ Object
- #resources_name ⇒ Object
-
#searchable_attributes ⇒ Object
Returns all columns that are searchable.
Constructor Details
#initialize(controller_path, module_definition = nil, custom_model = nil) ⇒ Resource
Returns a new instance of Resource.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/alchemy/resource.rb', line 93 def initialize(controller_path, module_definition=nil, custom_model=nil) @controller_path = controller_path @module_definition = module_definition @model = (custom_model or guess_model_from_controller_path) self.skipped_attributes = model.respond_to?(:skipped_alchemy_resource_attributes) ? model.skipped_alchemy_resource_attributes : DEFAULT_SKIPPED_ATTRIBUTES self.restricted_attributes = model.respond_to?(:restricted_alchemy_resource_attributes) ? model.restricted_alchemy_resource_attributes : [] if model.respond_to?(:alchemy_resource_relations) if not model.respond_to?(:reflect_on_all_associations) raise MissingActiveRecordAssociation end store_model_associations map_relations end end |
Instance Attribute Details
#model ⇒ Object (readonly)
Returns the value of attribute model.
88 89 90 |
# File 'lib/alchemy/resource.rb', line 88 def model @model end |
#model_associations ⇒ Object
Returns the value of attribute model_associations.
87 88 89 |
# File 'lib/alchemy/resource.rb', line 87 def model_associations @model_associations end |
#resource_relations ⇒ Object
Returns the value of attribute resource_relations.
87 88 89 |
# File 'lib/alchemy/resource.rb', line 87 def resource_relations @resource_relations end |
#restricted_attributes ⇒ Object
Returns the value of attribute restricted_attributes.
87 88 89 |
# File 'lib/alchemy/resource.rb', line 87 def restricted_attributes @restricted_attributes end |
#skipped_attributes ⇒ Object
Returns the value of attribute skipped_attributes.
87 88 89 |
# File 'lib/alchemy/resource.rb', line 87 def skipped_attributes @skipped_attributes end |
Instance Method Details
#attributes ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/alchemy/resource.rb', line 142 def attributes @_attributes ||= self.model.columns.collect do |col| unless self.skipped_attributes.include?(col.name) { name: col.name, type: resource_column_type(col), relation: resource_relation(col.name) }.delete_if { |k, v| v.nil? } end end.compact end |
#editable_attributes ⇒ Object
154 155 156 |
# File 'lib/alchemy/resource.rb', line 154 def editable_attributes attributes.reject { |h| self.restricted_attributes.map(&:to_s).include?(h[:name].to_s) } end |
#engine_name ⇒ Object
170 171 172 |
# File 'lib/alchemy/resource.rb', line 170 def engine_name @module_definition and @module_definition['engine_name'] end |
#help_text_for(attribute) ⇒ Object
Returns a help text for resource’s form
Example:
de:
alchemy:
resource_help_texts:
my_resource_name:
attribute_name: This is the fancy help text
184 185 186 187 188 |
# File 'lib/alchemy/resource.rb', line 184 def help_text_for(attribute) ::I18n.translate!(attribute[:name], :scope => [:alchemy, :resource_help_texts, resource_name]) rescue ::I18n::MissingTranslationData false end |
#in_engine? ⇒ Boolean
166 167 168 |
# File 'lib/alchemy/resource.rb', line 166 def in_engine? not self.engine_name.nil? end |
#model_association_names ⇒ Object
Returns an array of underscored association names
135 136 137 138 139 140 |
# File 'lib/alchemy/resource.rb', line 135 def model_association_names return unless model_associations model_associations.map do |assoc| assoc.name.to_sym end end |
#namespace_for_scope ⇒ Object
127 128 129 130 131 |
# File 'lib/alchemy/resource.rb', line 127 def namespace_for_scope namespace_array = namespace_diff namespace_array.delete(engine_name) if in_engine? namespace_array end |
#namespaced_resource_name ⇒ Object
120 121 122 123 124 125 |
# File 'lib/alchemy/resource.rb', line 120 def namespaced_resource_name return @_namespaced_resource_name unless @_namespaced_resource_name.nil? resource_name_array = resource_array resource_name_array.delete(engine_name) if in_engine? @_namespaced_resource_name = resource_name_array.join('_').singularize end |
#resource_array ⇒ Object
108 109 110 |
# File 'lib/alchemy/resource.rb', line 108 def resource_array @_resource_array ||= controller_path_array.reject { |el| el == 'admin' } end |
#resource_name ⇒ Object
116 117 118 |
# File 'lib/alchemy/resource.rb', line 116 def resource_name @_resource_name ||= resources_name.singularize end |
#resources_name ⇒ Object
112 113 114 |
# File 'lib/alchemy/resource.rb', line 112 def resources_name @_resources_name ||= resource_array.last end |
#searchable_attributes ⇒ Object
Returns all columns that are searchable
For now it only uses string type columns
162 163 164 |
# File 'lib/alchemy/resource.rb', line 162 def searchable_attributes self.attributes.select { |a| a[:type].to_sym == :string } end |