module Ropenlayer module ActsAs # acts_as_map: give to the model map construiction and build facilities. # # class Team < ActiveRecord::Base # acts_as_map :default_layer => :google_streets # end # # Avalaible options: # # - :longitude, :latitude, :zoom : Default starting values for map. Can be a float (latitude, longitude) or integer (zoom) object, so values will be parsed as them are. # A Symbol, in this case, ropenlayer will send :symbol method to mapper instance, or a valid Proc object # - :default_layer: Default layer for visualization. For avalaible layers see Ropenlayer::Openlayer::Map # - :nodes: Relations which can be pointed on the map. Relations are expected to have acts_as_localizable_on behaviour in their class # # Examples # # Build a Map with static features, just for localization # # class Team < ActiveRecord::Base # acts_as_map :default_layer => :google_streets, :latitude => 40000.23, :longitude => 54555.233, :zoom => 3 # end # # Build a Map with Proc calls to perform automatic localization on forms # # class Team < ActiveRecord # # has_many :offices # has_one :main_office, :conditions => { :main => true } # # acts_as_map :default_layer => :google_streets, :longitude => :main_office_longitude, :latitude => :main_office_latitude, :zoom => :main_office_zoom # :nodes => [ :offices ] # # def main_office_longitude # main_office.longitude # end # # def main_office_longitude=(value) # main_office_longitude = value # end # # def main_office_latitude # main_office.latitude # end # # def main_office_latitude=(value) # main_office.latitude = value # end # # def main_office_longitude # main_office.longitude # end # # def main_office_zoom=(value) # main_office.zoom = value # end # # end # module Mapper class << self def included(base) base.class_eval do base.send :include, InstanceMethods localizable = [:longitude, :latitude, :zoom ].inject(true) do |all_positions, position| all_positions = false unless self.ropenlayer_mapper_config[position].is_a?(Symbol) and self.attribute_method?(self.ropenlayer_mapper_config[position]) all_positions end send("class_variable_set", "@@localizable", true) if localizable end end module InstanceMethods # return ropenlayer_map instance or create a new one with the given data # options can be override def ropenlayer_map(options = {}) container_id = options.delete(:container_id) || "#{ self.class.name.underscore }_ropenlayer_map" @ropenlayer_map.nil? ? build_ropenlayer_map(container_id, options) : @ropenlayer_map end def build_ropenlayer_map(container_id, map_config = {}) config = self.class.ropenlayer_mapper_config.merge(map_config) config = set_map_localization(config) config = parse_nodes(config) @ropenlayer_map = Ropenlayer::Openlayer::Map.new(container_id, config) end # Ensure model acts_as_mapper def ensure_mapper_model error = "Model `#{ self.class }` doesnt acts as map" raise error unless self.class.methods.include?("acts_as_mapper") and self.class.acts_as_mapper end # Ensure model can be localizable def ensure_localizable_model error = "Model `#{ self.class }` is not localizable. To use this facility, latitude, longitude and zoom should be mapped to a valid instance methods" raise error unless self.class.methods.include?("localizable") and self.class.localizable end private # Parse nodes from nodes relations # It will call node_data method to any record on relation def parse_nodes(config) node_relations = config[:nodes] || [] config[:draw_features] = [] node_relations.each do |relation| config[:draw_features].concat(self.send(relation).map(&:node_data).select{ |node| not node.nil? }) end config end # Set default localization if avalaible # Longitude and latitude are parsed by default as float if Proc or Symbol are not defined. Zoom, by default, as integer def set_map_localization(config) defult_localization_values = { :longitude => :to_f, :latitude => :to_f, :zoom => :to_i, :default_layer => :to_sym } defult_localization_values.each do |name, default_method| eval_value = config.delete(name) value = 0 case eval_value when Symbol value = self.send(eval_value) when Proc value = eval_value.call(self) else value = eval_value.send(default_method) end config[name] = value unless value.to_i.zero? end config end end end end end end