app/controllers/releaf/base_controller.rb in releaf-0.1.1 vs app/controllers/releaf/base_controller.rb in releaf-0.1.2
- old
+ new
@@ -1,129 +1,50 @@
module Releaf
+ class FeatureDisabled < StandardError; end
+
class BaseController < BaseApplicationController
+ helper_method :build_secondary_panel_variables,
+ :fields_to_display,
+ :resource_class,
+ :find_parent_template,
+ :has_template?,
+ :list_action,
+ :render_field_type,
+ :render_parent_template,
+ :secondary_panel,
+ :current_feature,
+ :resource_to_text,
+ :resource_to_text_method
+
before_filter do
filter_templates
set_locale
setup
end
- def setup
- @controller = self # this is used later in views
- @features = { :create => true, :show => true, :edit => true, :destroy => true}
- @panel_layout = true
- @continuous_scroll = false
- @items_per_page = 40
- end
- def secondary_panel
- return '' unless @panel_layout
- @_secondary_panel ||= render_to_string( :partial => "secondary_panel", :layout => false, :locals => build_secondary_panel_variables)
- end
-
- def build_secondary_panel_variables
- menu_item_name = self.class.name.underscore.sub(/_controller$/, '')
-
- # if this item is defined in main menu, then there will be no altmenu
- # defined for it in alt menu, instead this method should be overriden in
- # particular controller to return structure needed to render alt menu
- return {} if Releaf.main_menu.include? menu_item_name
-
- # if this item was not found in main menu, then we need to find it in one
- # of alt menus. This way we'll know which alt menu to render.
- base_menus = Releaf.main_menu.reject { |item| item[0] != '*' }
- base_menus.each do |base_menu_name|
- if view_context.base_menu_items(base_menu_name).include?(menu_item_name)
- build_menu = { :menu => {} }
-
- base_menu = Releaf.base_menu[base_menu_name]
-
- base_menu.each do |section|
- section_name = section[0].to_sym
- build_menu[:menu][section_name] = []
- section[1].each do |item|
- build_menu[:menu][section_name].push({:controller => item.split(/#/, 2).first})
- end
- end
-
- return build_menu
- end
+ # Helper that returns current feature
+ def current_feature
+ case params[:action].to_sym
+ when :index
+ return :index
+ when :new, :create
+ return :create
+ when :edit, :update
+ return :edit
+ when :destroy, :confirm_destroy
+ return :destroy
+ else
+ return params[:action].to_sym
end
-
- # coundn't find current controller in base_menu
- return {}
end
- def current_object_class
- @_current_object_class ||= self.class.name.split('::').last.sub(/\s?Controller$/, '').classify.constantize
- end
- def current_object_class_name
- current_object_class.name.underscore.tr('/', '_')
- end
-
- def columns view=params[:action]
- cols = current_object_class.column_names - %w[id created_at updated_at encrypted_password position]
- unless %w[new edit update create].include? view
- cols -= %w[password password_confirmation]
- end
- return cols
- end
-
- def list_action
- if !cookies['base_module:list_action'].blank?
- feature = cookies['base_module:list_action']
- if feature == 'confirm_destroy'
- feature = 'destroy'
- end
- feature = feature.to_sym
- if !@features[ feature ].blank?
- return cookies['base_module:list_action']
- end
- end
- if !@features[ :show ].blank?
- return 'show'
- end
- return 'edit';
- end
-
- def has_template( name )
- lookup_context.template_exists?( name, lookup_context.prefixes, false )
- end
-
- def find_parent_template( name )
- lookup_context.find_template( name, lookup_context.prefixes.slice( 1, lookup_context.prefixes.length ), false )
- end
-
- def render_parent_template( name, locals = {} )
- template = find_parent_template( name )
- if template.blank?
- return 'blank'
- end
- arguments = { :layout => false, :locals => locals, :template => template.virtual_path }
- return render_to_string( arguments ).html_safe
- end
-
- def input_type_for( column_type, name )
- input_type = 'text'
- case column_type
- when :boolean
- input_type = 'checkbox'
- when :text
- input_type = 'textarea'
- if name.end_with?( '_html' )
- input_type = 'richtext'
- end
- end
- return input_type
- end
-
- # actions
-
def autocomplete
- authorize! :edit, current_object_class
+ authorize! :edit, resource_class
- c_obj = current_object_class
+ c_obj = resource_class
if params[:query_field] and params[:q] and params[:field] #and params[:field] =~ /_id\z/ and c_obj.column_names.include?(params[:field]) and c_obj.respond_to?(:reflect_on_association) and c_obj.reflect_on_association(params[:field].sub(/_id\z/, '').to_sym)
obj = c_obj.reflect_on_association(params[:field].sub(/_id\z/, '').to_sym).klass
obj_fields = obj.column_names
@@ -153,35 +74,41 @@
query = obj.where(sql.join(' AND '), sql_params).order(order_by.join(', '))
matching_items_count = query.count
list = query.limit(20)
- @items = []
- list.each do |item|
- @items.push({ :id => item.id, :text => item.to_text })
+ @resources = []
+ list.each do |resource|
+ @resources.push({ :id => resource.id, :text => resource.to_text })
end
respond_to do |format|
- format.json { render :json => {:matching_items_count => matching_items_count, :query => params[:q], :results => @items } }
+ format.json { render :json => {:matching_items_count => matching_items_count, :query => params[:q], :results => @resources } }
end
else
respond_to do |format|
format.json { raise }
end
end
end
def index
- authorize! :list, current_object_class
- if current_object_class.respond_to?( :filter )
- @list = current_object_class.filter(:search => params[:search])
+ authorize! :list, resource_class
+ if resource_class.respond_to? :filter
+ @resources = resource_class.filter(:search => params[:search])
else
- @list = current_object_class
+ @resources = resource_class
end
- @list = @list.page( params[:page] ).per_page( @items_per_page )
- if !params[:ajax].blank?
+
+ if resource_class.respond_to? :order_by
+ @resources = @resources.order_by(valid_order_by)
+ end
+
+ @resources = @resources.includes(relations_for_includes).page( params[:page] ).per_page( @resources_per_page )
+
+ unless params[:ajax].blank?
render :layout => false
end
end
def urls
@@ -195,93 +122,427 @@
end
end
end
def new
- authorize! :create, current_object_class
- @item = current_object_class.new
+ authorize! :create, resource_class
+ raise FeatureDisabled unless @features[:create]
+ @resource = resource_class.new
end
def show
- @item = current_object_class.find(params[:id])
- authorize! :show, @item
+ @resource = resource_class.includes(relations_for_includes).find(params[:id])
+ authorize! :show, @resource
+ raise FeatureDisabled unless @features[:show]
end
def edit
- @item = current_object_class.find(params[:id])
- authorize! :edit, @item
+ @resource = resource_class.includes(relations_for_includes).find(params[:id])
+ authorize! :edit, @resource
+ raise FeatureDisabled unless @features[:edit]
end
def create
- authorize! :create, current_object_class
- @item = current_object_class.new
+ authorize! :create, resource_class
+ raise FeatureDisabled unless @features[:create]
- if self.respond_to?(:"#{current_object_class_name}_params")
- variables = params.require( current_object_class_name ).permit( *self.send(:"#{current_object_class_name}_params", :update) )
- elsif @item.respond_to? :allowed_params
- variables = params.require( current_object_class_name ).permit( *@item.allowed_params(:create) )
- else
- variables = params.require( current_object_class_name ).permit( *current_object_class.column_names )
- end
+ @resource = resource_class.new
- @item.assign_attributes( variables )
+ @resource.assign_attributes required_params.permit(*resource_params)
respond_to do |format|
- if @item.save
- format.html { redirect_to url_for( :action => 'show', :id => @item.id ) }
+ if @resource.save
+ format.html { redirect_to url_for( :action => @features[:show] ? 'show' : 'index', :id => @resource.id ) }
else
format.html { render :action => "new" }
end
end
end
def update
- @item = current_object_class.find(params[:id])
- authorize! :edit, @item
+ @resource = resource_class.find(params[:id])
+ authorize! :edit, @resource
+ raise FeatureDisabled unless @features[:edit]
- if self.respond_to?(:"#{current_object_class_name}_params")
- variables = params.require( current_object_class_name ).permit( *self.send(:"#{current_object_class_name}_params", :update) )
- elsif @item.respond_to? :allowed_params
- variables = params.require( current_object_class_name ).permit( *@item.allowed_params(:update) )
- else
- variables = params.require( current_object_class_name ).permit( *current_object_class.column_names )
- end
-
respond_to do |format|
- if @item.update_attributes( variables )
- format.html { redirect_to url_for( :action => 'show', :id => @item.id ) }
+ if @resource.update_attributes( required_params.permit(*resource_params) )
+ format.html { redirect_to url_for( :action => @features[:show] ? 'show' : 'index', :id => @resource.id ) }
else
format.html { render :action => "edit" }
end
end
end
def confirm_destroy
- @item = current_object_class.find(params[:id])
- authorize! :destroy, @item
+ @resource = resource_class.find(params[:id])
+ authorize! :destroy, @resource
+ raise FeatureDisabled unless @features[:destroy]
end
def destroy
- @item = current_object_class.find(params[:id])
- authorize! :destroy, @item
- @item.destroy
+ @resource = resource_class.find(params[:id])
+ authorize! :destroy, @resource
+ raise FeatureDisabled unless @features[:destroy]
+ @resource.destroy
respond_to do |format|
format.html { redirect_to url_for( :action => 'index' ) }
end
end
+
+
+
+
+ # Helper methods ##############################################################################
+
+
+ def list_action
+ if !cookies['base_module:list_action'].blank?
+ feature = cookies['base_module:list_action']
+ if feature == 'confirm_destroy'
+ feature = 'destroy'
+ end
+ feature = feature.to_sym
+ if @features[feature]
+ return cookies['base_module:list_action']
+ end
+ end
+
+ return 'show' if @features[:show]
+ return 'edit'
+ end
+
+ # Defines which fields/associations should be rendered.
+ #
+ # By default renders resource columns except few (check source).
+ #
+ # You can override this method to make it possible to render pretty complex
+ # views which inludes nested fields.
+ #
+ # To render field you simply need to add it's name to array.
+ #
+ # belongs_to relations will be automatically rendered (by default) as
+ # select field. For belongs_to to be recognized you need to use Integer
+ # field that ends with <tt>_id</tt>
+ #
+ # You can also render has_many associations. For these associations you
+ # need to add either association name, or a Hash. Hash keys must match
+ # association name, hash value must be array with nested fields to be
+ # rendered.
+ #
+ # NOTE: currently if you add has_many associations name to array, then it
+ # will render all fields (except created_at etc.) including <tt>belongs_to
+ # :parent</tt>. This is know bug https://github.com/cubesystems/releaf/issues/64
+ #
+ # @example
+ # def fields_to_display
+ # case params[:action]
+ # when 'edit', 'update', 'create', 'new'
+ # return [
+ # 'name',
+ # 'category_id',
+ # 'description',
+ # {'offer_card_types' => ['card_type_id', 'name', 'description']},
+ # 'show_banner',
+ # 'published',
+ # 'item_count',
+ # {'images' => ['image_uid']},
+ # 'partner_id',
+ # 'offer_checkout_places' => ['checkout_place_id']
+ # ]
+ # else
+ # return super
+ # end
+ # end
+ #
+ # Fields will be rendered in same order as specified in array
+ #
+ # @return array that represent which fields to render
+ def fields_to_display
+ cols = resource_class.column_names - %w[id created_at updated_at encrypted_password position]
+ unless %w[new edit update create].include? params[:action]
+ cols -= %w[password password_confirmation]
+ end
+ return cols
+ end
+
+
+ def secondary_panel
+ return '' unless @panel_layout
+ @_secondary_panel ||= render_to_string( :partial => "secondary_panel", :layout => false, :locals => build_secondary_panel_variables)
+ end
+
+ def build_secondary_panel_variables
+ menu_item_name = self.class.name.underscore.sub(/_controller$/, '')
+
+ # if this item is defined in main menu, then there will be no altmenu
+ # defined for it in alt menu, instead this method should be overriden in
+ # particular controller to return structure needed to render alt menu
+ return {} if Releaf.main_menu.include? menu_item_name
+
+ # if this item was not found in main menu, then we need to find it in one
+ # of alt menus. This way we'll know which alt menu to render.
+ base_menus = Releaf.main_menu.reject { |item| item[0] != '*' }
+ base_menus.each do |base_menu_name|
+ if view_context.base_menu_items(base_menu_name).include?(menu_item_name)
+ build_menu = { :menu => {} }
+
+ base_menu = Releaf.base_menu[base_menu_name]
+
+ base_menu.each do |section|
+ section_name = section[0].to_sym
+ build_menu[:menu][section_name] = []
+ section[1].each do |item|
+ build_menu[:menu][section_name].push({:controller => item.split(/#/, 2).first})
+ end
+ end
+
+ return build_menu
+ end
+ end
+
+ # coundn't find current controller in base_menu
+ return {}
+ end
+
+ # Tries to return resource class.
+ #
+ # If it fails to return proper resource class for your controller, or your
+ # controllers name has no relation to resource class name, then simply
+ # override this method to return class that you want.
+ #
+ # @return class
+ def resource_class
+ @resource_class ||= self.class.name.split('::').last.sub(/Controller$/, '').classify.constantize
+ end
+
+
+ # Cheheck if there is a template in lookup_context with given name.
+ #
+ # @return `true` or `false`
+ def has_template? name
+ lookup_context.template_exists?( name, lookup_context.prefixes, false )
+ end
+
+ def find_parent_template( name )
+ lookup_context.find_template( name, lookup_context.prefixes.slice( 1, lookup_context.prefixes.length ), false )
+ end
+
+ def render_parent_template( name, locals = {} )
+ template = find_parent_template( name )
+ if template.blank?
+ return 'blank'
+ end
+ arguments = { :layout => false, :locals => locals, :template => template.virtual_path }
+ return render_to_string( arguments ).html_safe
+ end
+
+ # Helps to determinate which template to render in :show and :edit feature
+ # for given objects attribute.
+ #
+ # @return [field_type, use_i18n]
+ #
+ # where field_type is a string representing field type
+ # and use_i18n is a `true` or `false`. If use_i18n is true, then template
+ # with localization features should be used (if exists)
+ #
+ # This helper is used by views.
+ #
+ # @todo document rendering conventions
+ def render_field_type( obj, attribute_name )
+ field_type = nil
+ use_i18n = false
+ obj_class = obj.class
+
+ if obj_class.respond_to?(:translations_table_name)
+ use_i18n = obj_class.translates.include?(attribute_name.to_sym)
+ end
+
+ column_type = :string
+ if attribute_name.to_s =~ /^#{Releaf::Node::COMMON_FIELD_NAME_PREFIX}/
+ column_type = obj.common_field_field_type(attribute_name)
+ else
+ column_type = obj_class.columns_hash[attribute_name.to_s].try(:type) || :string
+ end
+
+ case column_type.to_sym
+ when :boolean
+ field_type = 'boolean'
+
+ when :string
+ case attribute_name.to_s
+ when /(thumbnail|image|photo|picture|avatar|logo|banner|icon)_uid$/
+ field_type = 'image'
+
+ when /_uid$/
+ field_type = 'file'
+
+ when /password/, 'pin'
+ field_type = 'password'
+
+ when /_email$/, 'email'
+ field_type = 'email'
+
+ when /_link$/, 'link'
+ field_type = 'link'
+
+ else
+ field_type = 'text'
+ end
+
+ when :integer
+ if attribute_name.to_s =~ /_id$/ and obj_class.reflect_on_association( attribute_name[0..-4].to_sym )
+ field_type = 'item'
+ else
+ field_type = 'text'
+ end
+
+ when :text
+ case attribute_name.to_s
+ when /_(url|homepage)$/, 'homepage', 'url'
+ field_type = 'url'
+
+ when /_link$/, 'url'
+ field_type = 'link_or_url'
+
+ when /_html$/, 'html'
+ field_type = 'richtext'
+
+ else
+ field_type = 'textarea'
+ end
+
+ when :datetime
+ field_type = 'datetime'
+
+ when :date
+ field_type = 'date'
+
+ when :time
+ field_type = 'time'
+
+ end
+
+ return [field_type || 'text', use_i18n]
+ end
+
+ # calls `#to_text` on resource if resource supports it. Otherwise calls
+ # fallback method
+ def resource_to_text resource, fallback=:to_s
+ resource.send resource_to_text_method(resource, fallback)
+ end
+
+ # @return `:to_text` if resource supports `#to_text`, otherwise fallback.
+ def resource_to_text_method resource, fallback=:to_s
+ if resource.respond_to?(:to_text)
+ return :to_text
+ else
+ Rails.logger.warn "Re:Leaf: #{resource.class.name} doesn't support #to_text method. Please define it"
+ return fallback
+ end
+ end
+
+ protected
+
+ def required_params
+ params.require(:resource)
+ end
+
+ # Called before each request by before_filter.
+ # It sets various instance variables, that are later used in views and # controllers
+ #
+ # == Defines
+ # @fetures::
+ # Hash with symbol keys and boolean values. Each key represents action
+ # (currently only `:edit`, `:create`, `:show`, `:destroy` are supported). If one
+ # of features is disabled, then routing to it will raise <tt>Releaf::FeatureDisabled</tt>
+ # error
+ #
+ # @continuous_scroll::
+ # Boolean. If set to `true` will enable continuous scrool in `#index` view
+ #
+ # @resources_per_page::
+ # Integer - sets the number of resources to display on `#index` view
+ #
+ # To change controller settings `setup` method should be overriden like this
+ #
+ # @example
+ # def setup
+ # super
+ # @fetures[:show] = false
+ # @resources_per_page = 20
+ # end
+ def setup
+ @features = {
+ :edit => true,
+ :create => true,
+ :show => true,
+ :destroy => true
+ }
+ @continuous_scroll = false
+ @panel_layout = true
+ @resources_per_page = 40
+ end
+
+ # Returns which resource attributes can be updated with mass assignment.
+ #
+ # The resulting array will be passed to strong_parameters ``permit``
+ def resource_params
+ return unless %w[create update].include? params[:action]
+ resource_class.column_names
+ end
+
private
+ def relations_for_includes
+ return nil
+ # XXX there's a problem with relations that have conditions with proc.
+ # If you refer to models attribute in proc, this function will break.
+ # As temp workaround we'll simply skip including relations that have conditions for now.
+ rels = []
+ fields_to_display.each do |field|
+ if (field.is_a? String or field.is_a? Symbol) and field =~ /_id$/
+ reflection = resource_class.reflect_on_association(field[0..-4].to_sym)
+ next if reflection.blank?
+ next unless reflection.conditions.blank?
+ rels.push field[0..-4]
+ elsif field.is_a? Hash
+ field.keys.each do |key|
+ if key =~ /_id$/
+ reflection = resource_class.reflect_on_association(key[0..-4].to_sym)
+ next if reflection.blank?
+ next unless reflection.conditions.blank?
+ rels.push key[0..-4] if resource_class.reflect_on_association(key[0..-4].to_sym)
+ else
+ reflection = resource_class.reflect_on_association(key.to_sym)
+ next if reflection.blank?
+ next unless reflection.conditions.blank?
+ rels.push key if resource_class.reflect_on_association(key.to_sym)
+ end
+ end
+ end
+ end
+ return rels
+ end
+
+ def valid_order_by
+ return nil if params[:order_by].blank?
+ return nil unless resource_class.column_names.include?(params[:order_by].sub(/-reverse$/, ''))
+ return resource_class.table_name + '.' + params[:order_by].sub(/-reverse$/, ' DESC')
+ end
+
def set_locale
I18n.locale = if params[:locale] && Settings.i18n_locales.include?(params[:locale])
params[:locale]
elsif cookies[:locale] && Settings.i18n_locales.include?(cookies[:locale])
cookies[:locale]
else
I18n.default_locale
end
+
+ Releaf::Globalize3::Fallbacks.set
end
def filter_templates
filter_templates_from_hash params
end