Changeset 1121

Show
Ignore:
Timestamp:
2008-07-25 10:01:11 (6 months ago)
Author:
gaspard
Message:

Changed edit popup and zana.js so that all fields can be active. Just add "editor_preview='true'" to [show]. You can also use "ep='true'".
Added CommentQuery? to filter comments: <r:comments order='created_at desc'/>. This is just the beginning of cross-class queries !

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/app/models/node.rb

    r1103 r1121  
    10041004    return unless str && str != "" 
    10051005    self[:name] = str.url_name 
     1006  end 
     1007   
     1008  # Return current discussion id 
     1009  def discussion_id 
     1010    discussion ? discussion[:id] : nil 
    10061011  end 
    10071012   
  • trunk/app/views/versions/edit.rhtml

    r1053 r1121  
    3838  Zena.resizeElement('node_v_text'); 
    3939} 
     40 
     41$('node_form').getElements().each(function(input, index) { 
     42    new Form.Element.Observer(input, 3, function(element, value) { 
     43      <%= "opener.Zena.editor_preview('#{preview_version_url(:node_id=>@node[:zip], :id=>(@node.v_number || 0), :_method => :put, :escape => false)}',element,value);" %> 
     44    }); 
     45}); 
     46 
    4047<%= javascript_end %> 
    41 <%# observe all fields ? new Form.Observer('node_form', 3, function(element,value){ opener.Zena.editor_preview(element,value)}); %> 
    42 <% ['v_text', 'v_summary', 'v_title'].each do |field| -%> 
    43 <%= observe_field("node_#{field}", :frequency=>3, :function=>"opener.Zena.editor_preview('#{preview_version_url(:node_id=>@node[:zip], :id=>(@node.v_number || 0), :_method => :put, :key => field, :escape => false)}',element,value)") %> 
    44 <% end -%> 
  • trunk/app/views/versions/preview.rjs

    r883 r1121  
     1key = "#{@key}#{@node[:zip]}" 
     2 
    13page << "try {" 
    2 @value = zazen(@value, :pretty_code=>true, :preview => true) if @key != 'v_title' 
    3 page.replace_html   "#{@key}#{@node[:zip]}", @value 
     4page << "if ($('#{key}').classNames().include('zazen')) {" 
     5page.replace_html key, zazen(@value, :pretty_code=>true, :preview => true) 
     6page << "} else {" 
     7page.replace_html key, @value 
     8page << "}" 
    49page << "} catch(err) {}" 
  • trunk/config/environment.rb

    r1108 r1121  
    8787require File.join(lib_path, 'base_additions') 
    8888require File.join(lib_path, 'node_query') 
     89require File.join(lib_path, 'comment_query') 
    8990ZazenParser = Parser.parser_with_rules(Zazen::Rules, Zazen::Tags) 
    9091ZafuParser  = Parser.parser_with_rules(Zafu::Rules, Zena::Rules, Zafu::Tags, Zena::Tags) 
  • trunk/lib/node_query.rb

    r1111 r1121  
    44class NodeQuery < QueryBuilder 
    55  attr_reader :context, :uses_node_name 
    6   set_main_table :nodes 
     6  set_main_table 'nodes' 
     7  set_main_class 'Node' 
     8   
    79  load_custom_queries File.join(File.dirname(__FILE__), 'custom_queries') 
    810 
     
    3032  def default_order_clause 
    3133    "position ASC, name ASC" 
     34  end 
     35   
     36  def default_context_filter 
     37    'self' 
    3238  end 
    3339   
     
    94100      unless fields 
    95101        if klass = Node.get_class(rel) 
    96           parse_context('self') unless context 
     102          parse_context(default_context_filter) unless context 
    97103          @where << "#{table}.kpath LIKE '#{klass.kpath}%'" 
    98104          return true 
     
    105111      @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" 
    106112      true 
     113    end 
     114     
     115    def parse_change_class(rel, is_last) 
     116      case rel 
     117      when 'comments' 
     118        if is_last 
     119          # no need to load discussions, versions and all the mess 
     120          add_table('comments') 
     121          @where << "#{table('comments')}.discussion_id = #{map_parameter('discussion_id')}" 
     122          return CommentQuery # class change 
     123        else 
     124          # parse_context(default_context_filter, true) if is_last 
     125          # after_parse 
     126          @errors << "comments with join not implemented" 
     127          return nil 
     128        end 
     129      else 
     130        return nil 
     131      end 
    107132    end 
    108133     
     
    111136      if rel == main_table || rel == 'children' 
    112137        # dummy clauses 
    113         parse_context('self') unless context 
     138        parse_context(default_context_filter) unless context 
    114139        return :void 
    115140      end 
     
    214239        @uses_node_name = true 
    215240        "\#{#{@node_name}.get_#{fld}}" 
    216       when 'id', 'parent_id' 
     241      when 'id', 'parent_id', 'discussion_id' 
    217242        @uses_node_name = true 
    218243        "\#{#{@node_name}.#{fld}}" 
     
    251276      end 
    252277    end 
    253      
    254     def class_from_table(table_name) 
    255       case table_name 
    256       when 'nodes' 
    257         Node 
    258       when 'versions' 
    259         Version 
    260       when 'comments' 
    261         Comment 
    262       when 'data_entries' 
    263         DataEntry 
    264       else 
    265         # ? error 
    266         Object 
    267       end 
    268     end 
    269      
     278         
    270279    def parse_custom_query_argument(key, value) 
    271280      return nil unless value 
     
    341350        end 
    342351        query = NodeQuery.new(pseudo_sql, opts.merge(:custom_query_group => visitor.site.host)) 
    343         [query.to_sql, query.errors, query.uses_node_name, query.result_class] 
     352        [query.to_sql, query.errors, query.uses_node_name, query.main_class] 
    344353      end 
    345354    end 
     
    349358       
    350359      # Find a node and propagate visitor 
    351       def do_find(count, query, ignore_source = false
     360      def do_find(count, query, ignore_source = false, klass = Node
    352361        return nil if query.empty? 
    353362        return nil if (new_record? && !ignore_source) # do not run query (might contain nil id) 
    354         res = Node.find_by_sql(query) 
     363        res = klass.find_by_sql(query) 
    355364        if count == :all 
    356365          if res == [] 
  • trunk/lib/parser/lib/rules/zena.rb

    r1120 r1121  
    359359       
    360360      attribute = @params[:attr] || @params[:tattr] || @params[:date] 
     361       
     362      if (@params[:edit_preview] || @params[:ep]) == 'true' 
     363        @html_tag_params[:id] = "#{attribute}#{erb_node_id}" 
     364        @html_tag ||= 'span' 
     365      end 
     366       
    361367      if @params[:edit] == 'true' && !['url','path'].include?(attribute) 
    362368        "<% if #{node}.can_write? -%><span class='show_edit' id='#{erb_dom_id("_#{attribute}")}'>#{actions}<%= link_to_remote(#{attribute_method}, :url => edit_node_path(#{node_id}) + \"?attribute=#{attribute}&dom_id=#{dom_id("_#{attribute}")}#{@params[:publish] == 'true' ? '&publish=true' : ''}\", :method => :get) %></span><% else -%>#{actions}<%= #{attribute_method} %><% end -%>" 
     
    385391      else 
    386392        # error 
     393      end 
     394       
     395      @html_tag ||= 'div' 
     396       
     397      add_html_class('zazen') 
     398       
     399      if (@params[:edit_preview] || @params[:ep]) == 'true' 
     400        @html_tag_params[:id] = "#{attribute}#{erb_node_id}" 
    387401      end 
    388402       
     
    679693       
    680694      @html_tag ||= 'div' 
     695       
    681696      if @html_tag_params[:id] 
    682697        # add a sub-div 
     
    688703      end 
    689704       
    690       if c = @html_tag_params[:class] || @params[:class] 
    691         @html_tag_params[:class] = "#{c} zazen" 
    692       else 
    693         @html_tag_params[:class] = 'zazen' 
    694       end 
     705      add_html_class('zazen') 
     706       
    695707      unless @params[:empty] == 'true' 
    696708        out "#{pre}<% if #{node}.kind_of?(TextDocument); l = #{node}.content_lang -%>" 
     
    724736      end 
    725737       
    726       if c = @html_tag_params[:class] || @params[:class] 
    727         @html_tag_params[:class] = "#{c} zazen" 
    728       else 
    729         @html_tag_params[:class] = 'zazen' 
    730       end 
     738      add_html_class('zazen') 
     739       
    731740      unless @params[:or] 
    732741        text = @params[:text] ? @params[:text].inspect : node_attribute('v_summary') 
     
    19751984        return nil 
    19761985      end 
    1977        
    1978       res = "#{node_name}.do_find(#{count.inspect}, \"#{sql_query}\", #{!uses_node_name})" 
     1986      node_class_param = klass == Node ? '' : ", #{klass}" 
     1987      res = "#{node_name}.do_find(#{count.inspect}, \"#{sql_query}\", #{!uses_node_name}#{node_class_param})" 
    19791988      if params[:else] 
    19801989        else_query, else_klass = build_finder_for(count, params[:else], {}) 
     
    26492658    end 
    26502659     
     2660    # Add a class name to the html_tag 
     2661    def add_html_class(class_name) 
     2662      if klass = @html_tag_params[:class] 
     2663        @html_tag_params[:class] = "#{class_name} #{klass}" 
     2664      else 
     2665        @html_tag_params[:class] = class_name 
     2666      end 
     2667    end 
     2668     
    26512669    def render_html_tag(text,*append) 
    26522670      append ||= [] 
  • trunk/lib/query_builder/lib/query_builder.rb

    r1111 r1121  
    1010=end 
    1111class QueryBuilder 
    12   attr_reader :tables, :where, :errors, :join_tables, :distinct 
    13   @@main_table = 'objects' 
     12  attr_reader :tables, :where, :errors, :join_tables, :distinct, :final_parser 
     13  @@main_table = {} 
     14  @@main_class = {} 
    1415  @@custom_queries = {} 
    1516   
    1617  class << self 
    1718    def set_main_table(table_name) 
    18       @@main_table = table_name.to_s 
     19      @@main_table[self] = table_name.to_s 
     20    end 
     21     
     22    def set_main_class(main_class) 
     23      @@main_class[self] = main_class.to_s 
    1924    end 
    2025     
     
    4146      raise ArgumentError.new("invalid class for CustomQueries (#{klass})") 
    4247    end 
     48     
     49    def new(*args) 
     50      obj = super 
     51      obj.final_parser 
     52    end 
    4353  end 
    4454   
    4555  def initialize(query, opts = {}) 
    46     if query.kind_of?(Array) 
    47       @query = query[0] 
    48       if query.size > 1 
    49         alt_queries = query[1..-1].map {|q| self.class.new(q, opts.merge(:skip_after_parse => true))} 
    50       end 
     56    if opts[:pre_query] 
     57      init_with_pre_query(opts[:pre_query], opts[:elements]) 
    5158    else 
    52       @query = query 
    53     end 
    54      
    55    
    56     @tables  = [] 
    57     @join_tables = {} 
    58     @table_counter = {} 
    59     @where  = [] 
    60     # list of tables that need to be added for filter clauses (should be added only once per part) 
    61     @needed_tables = {} 
    62     # list of tables that need to be added through a join (should be added only once per part) 
    63     @needed_join_tables = {} 
    64    
    65     @errors  = [] 
    66    
    67     @main_table ||= 'objects' 
    68    
    69     @select  = [] 
    70    
    71     @ignore_warnings = opts[:ignore_warnings] 
    72      
    73     @ref_date = opts[:ref_date] ? "'#{opts[:ref_date]}'" : 'now()' 
    74      
    75     if @query == nil || @query == '' 
    76       elements = [main_table] 
    77     else 
    78       elements = @query.split(' from ') 
    79       last_element = elements.last 
    80       last_element, offset = last_element.split(' offset ') 
    81       last_element, limit  = last_element.split(' limit ') 
    82       last_element, order  = last_element.split(' order by ') 
    83       elements[-1], group  = last_element.split(' group by ') 
    84     end 
    85      
    86     if @@custom_queries[self.class] &&  
    87        @@custom_queries[self.class][opts[:custom_query_group]] &&  
    88        custom_query = @@custom_queries[self.class][opts[:custom_query_group]][extract_custom_query(elements)] 
    89       custom_query.each do |k,v| 
    90        instance_variable_set("@#{k}", prepare_custom_query_arguments(k.to_sym, v)) 
    91       end 
    92       # set table counters 
    93       @tables.each do |t| 
    94         base, as, tbl = t.split(' ') 
    95         @table_counter[base] ||= 0 
    96         @table_counter[base] += 1 if tbl 
    97       end 
    98       # parse filters 
    99       clause, filters = elements[-1].split(/\s+where\s+/) 
    100        
    101       parse_filters(filters) if filters 
    102       @order = parse_order_clause(order) if order 
    103     else 
    104       # In order to know the table names of the dependencies, we need to parse it backwards. 
    105       # We first find the closest elements, then the final ones. For example, "pages from project" we need 
    106       # project information before getting 'pages'.  
    107       elements.reverse! 
    108      
    109       elements.each_index do |i| 
    110         parse_part(elements[i], i == 0) # yes, is_last is first (parsing reverse) 
    111       end 
    112       @distinct ||= elements.size > 1 
    113       @select << "#{table}.*" 
    114        
    115       merge_alternate_queries(alt_queries) if alt_queries 
    116        
    117       @limit    = parse_limit_clause(opts[:limit] || limit) 
    118       @offset ||= parse_offset_clause(offset) 
    119  
    120  
    121       @group = parse_group_clause(group) if group 
    122       @order = parse_order_clause(order || default_order_clause) 
    123     end 
    124      
    125     after_parse unless opts[:skip_after_parse] 
    126     @where.compact! 
     59      init_with_query(query, opts) 
     60    end 
     61     
     62    parse_elements(@elements) 
    12763  end 
    12864   
     
    15288  end 
    15389   
    154   def result_class 
    155     class_from_table(current_table
     90  def main_class 
     91    Module.const_get(@@main_class[self.class]
    15692  end 
    15793   
     
    16399     
    164100    def main_table 
    165       @@main_table 
     101      @@main_table[self.class] 
    166102    end 
    167103   
    168104    def parse_part(part, is_last) 
    169       add_table(main_table) 
    170105       
    171106      rest,   context = part.split(' in ') 
    172107      clause, filters = rest.split(/\s+where\s+/) 
    173108       
    174       parse_filters(filters) if filters 
    175       parse_context(context, is_last) if context # .. in project 
    176       parse_relation(clause, context) 
     109      if @just_changed_class 
     110        # just changed class: parse filters && context 
     111        parse_filters(filters) if filters 
     112        @just_changed_class = false 
     113        return nil 
     114      elsif new_class = parse_change_class(clause, is_last) 
     115        if context 
     116          last_filter = @where.pop # pop/push is to keep queries in correct order (helps reading sql) 
     117          parse_context(context, true) 
     118          @where << last_filter 
     119        end 
     120        return new_class 
     121      else 
     122        add_table(main_table) 
     123        parse_filters(filters) if filters 
     124        parse_context(context, is_last) if context # .. in project 
     125        parse_relation(clause, context) 
     126        return nil 
     127      end 
    177128    end 
    178129     
     
    194145        elsif rest[0..0] == '(' 
    195146          unless allowed.include?(:par_open) 
    196             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     147            @errors << clause_error(clause, rest, res)  
    197148            return 
    198149          end 
     
    202153        elsif rest[0..0] == ')'   
    203154          unless allowed.include?(:par_close) 
    204             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     155            @errors << clause_error(clause, rest, res)  
    205156            return 
    206157          end 
     
    209160          par_count -= 1 
    210161          if par_count < 0 
    211             @errors << "invalid clause #{clause.inspect} near #{res.inspect}" 
     162            @errors << clause_error(clause, rest, res) 
    212163            return 
    213164          end 
     
    215166        elsif rest =~ /\A((>=|<=|<>|<|=|>)|((not\s+like|like|lt|le|eq|ne|ge|gt)\s+))/ 
    216167          unless allowed.include?(:op) 
    217             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     168            @errors << clause_error(clause, rest, res)  
    218169            return 
    219170          end 
     
    225176        elsif rest =~ /\A("|')([^\1]*?)\1/   
    226177          unless allowed.include?(:value) 
    227             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     178            @errors << clause_error(clause, rest, res)  
    228179            return 
    229180          end 
     
    233184        elsif rest =~ /\A(\d+|[\w:]+)\s+(second|minute|hour|day|week|month|year)s?/ 
    234185          unless allowed.include?(:value) 
    235             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     186            @errors << clause_error(clause, rest, res)  
    236187            return 
    237188          end 
     
    246197        elsif rest =~ /\A(\d+)/   
    247198          unless allowed.include?(:value) 
    248             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     199            @errors << clause_error(clause, rest, res)  
    249200            return 
    250201          end 
     
    254205        elsif rest =~ /\A(is\s+not\s+null|is\s+null)/ 
    255206          unless allowed.include?(:bool_op) 
    256             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     207            @errors << clause_error(clause, rest, res)  
    257208            return 
    258209          end 
     
    262213        elsif rest[0..7] == 'REF_DATE'   
    263214          unless allowed.include?(:value) 
    264             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     215            @errors << clause_error(clause, rest, res)  
    265216            return 
    266217          end 
     
    270221        elsif rest =~ /\A(\+|\-)/   
    271222          unless allowed.include?(:op) 
    272             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     223            @errors << clause_error(clause, rest, res)  
    273224            return 
    274225          end 
     
    278229        elsif rest =~ /\A(and|or)/ 
    279230          unless allowed.include?(:bool_op) 
    280             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     231            @errors << clause_error(clause, rest, res)  
    281232            return 
    282233          end 
     
    287238        elsif rest =~ /\A[\w:]+/ 
    288239          unless allowed.include?(:value) 
    289             @errors << "invalid clause #{clause.inspect} near #{res.inspect}"  
     240            @errors << clause_error(clause, rest, res)  
    290241            return 
    291242          end 
     
    299250          allowed = after_value 
    300251        else   
    301           @errors << "invalid clause #{clause.inspect} near #{res.inspect}" 
     252          @errors << clause_error(clause, rest, res) 
    302253          return 
    303254        end 
     
    314265     
    315266    def parse_order_clause(order) 
    316       return nil unless order 
     267      return @order unless order 
    317268      res = [] 
    318269       
     
    338289     
    339290    def parse_group_clause(field) 
    340       return nil unless field 
     291      return @group unless field 
    341292      if fld = map_field(field, table, :group) 
    342293        " GROUP BY #{fld}" 
     
    348299     
    349300    def parse_limit_clause(limit) 
    350       return nil unless limit 
     301      return @limit unless limit 
    351302      if limit.kind_of?(Fixnum) 
    352303        " LIMIT #{limit}" 
     
    363314     
    364315    def parse_offset_clause(offset) 
    365       return nil unless offset 
     316      return @offset unless offset 
    366317      if !@limit 
    367318        # TODO: raise error ? 
     
    456407       
    457408      alt_queries.each do |query| 
     409        next unless query.main_class == self.main_class # no mixed class target ! 
    458410        @errors += query.errors unless @ignore_warnings 
    459411        next unless query.valid? 
     
    502454    end 
    503455     
     456    # Map a field to be used inside a query 
     457    def field_or_param(fld, table_name = table, context = nil) 
     458      if fld =~ /^\d+$/ 
     459        return fld 
     460      elsif @select.join =~ / AS #{fld}/ 
     461        @select.each do |s| 
     462          if s =~ /\A(.*) AS #{fld}\Z/ 
     463            return context == :filter ? "(#{$1})" : fld 
     464          end 
     465        end 
     466      elsif table_name 
     467        map_field(fld, table_name, context) 
     468      else 
     469        map_parameter(fld) 
     470      end 
     471    end 
     472     
    504473    # ******** Overwrite these ********** 
    505474    def class_from_table(table_name) 
     
    507476    end 
    508477     
     478    def default_context_filter 
     479      raise NameError.new("default_context_filter not defined for class #{self.class}") 
     480    end 
     481     
     482    # Default sort order 
     483    def default_order_clause 
     484      nil 
     485    end 
     486     
     487    def after_parse 
     488      # do nothing 
     489    end 
     490     
     491    def parse_change_class(rel, is_last) 
     492      nil 
     493    end 
     494     
     495    def parse_relation(clause, context) 
     496      return nil 
     497    end 
     498     
     499    def context_filter_fields(clause, is_last = false) 
     500      nil 
     501    end 
     502     
     503    def parse_context(clause, is_last = false) 
     504       
     505      if fields = context_filter_fields(clause, is_last) 
     506        @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 
     507      else 
     508        @errors << "invalid context '#{clause}'" 
     509      end 
     510    end 
     511     
     512    # Map a litteral value to be used inside a query 
     513    def map_literal(value) 
     514      value.inspect 
     515    end 
     516     
     517     
     518    # Overwrite this and take car to check for valid fields. 
     519    def map_field(fld, table_name, context = nil) 
     520      if fld == 'id' 
     521        "#{table_name}.#{fld}" 
     522      else 
     523        # TODO: error, raise / ignore ? 
     524      end 
     525    end 
     526     
     527    def map_parameter(fld) 
     528      fld.to_s.upcase 
     529    end 
     530     
     531    # ******** And maybe overwrite these ********** 
    509532    def parse_custom_query_argument(key, value) 
    510533      return nil unless value 
     
    524547    end 
    525548     
    526     def default_context_filter(clause) 
    527       nil 
    528     end 
    529      
    530     # Default sort order 
    531     def default_order_clause 
    532       nil 
    533     end 
    534      
    535     def after_parse 
    536       # do nothing 
    537     end 
    538      
    539     def parse_relation(clause, context) 
    540       return nil 
    541     end 
    542      
    543     def context_filter_fields(clause, is_last = false) 
    544       nil 
    545     end 
    546      
    547     def parse_context(clause, is_last = false) 
    548        
    549       if fields = context_filter_fields(clause, is_last) 
    550         @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 
    551       else 
    552         @errors << "invalid context '#{clause}'" 
    553       end 
    554     end 
    555      
    556     # Map a litteral value to be used inside a query 
    557     def map_literal(value) 
    558       value.inspect 
    559     end 
    560      
    561     # Map a field to be used inside a query 
    562     def field_or_param(fld, table_name = table, context = nil) 
    563       if fld =~ /^\d+$/ 
    564         return fld 
    565       elsif @select.join =~ / AS #{fld}/ 
    566         @select.each do |s| 
    567           if s =~ /\A(.*) AS #{fld}\Z/ 
    568             return context == :filter ? "(#{$1})" : fld 
    569           end 
    570         end 
    571       elsif table_name 
    572         map_field(fld, table_name, context) 
    573       else 
    574         map_parameter(fld) 
    575       end 
    576     end 
    577      
    578     # Overwrite this and take car to check for valid fields. 
    579     def map_field(fld, table_name, context = nil) 
    580       if fld == 'id' 
    581         "#{table_name}.#{fld}" 
    582       else 
    583         # TODO: error, raise / ignore ? 
    584       end 
    585     end 
    586      
    587     def map_parameter(fld) 
    588       fld.to_s.upcase 
     549  private 
     550     
     551    def parse_elements(elements) 
     552      # "final_parser" is the parser who will respond to 'to_sql'. It might be a sub-parser for another class. 
     553      @final_parser = self 
     554       
     555      if @@custom_queries[self.class] &&  
     556         @@custom_queries[self.class][@opts[:custom_query_group]] &&  
     557         custom_query = @@custom_queries[self.class][@opts[:custom_query_group]][extract_custom_query(elements)] 
     558        custom_query.each do |k,v| 
     559         instance_variable_set("@#{k}", prepare_custom_query_arguments(k.to_sym, v)) 
     560        end 
     561        # set table counters 
     562        @tables.each do |t| 
     563          base, as, tbl = t.split(' ') 
     564          @table_counter[base] ||= 0 
     565          @table_counter[base] += 1 if tbl 
     566        end 
     567        # parse filters 
     568        clause, filters = elements[-1].split(/\s+where\s+/) 
     569 
     570        parse_filters(filters) if filters 
     571        @order = parse_order_clause(@offset_limit_order_group[:order]) 
     572      else 
     573        i, new_class = 0, nil 
     574        elements.each_index do |i| 
     575          break if new_class = parse_part(elements[i], i == 0) # yes, is_last is first (parsing reverse) 
     576        end 
     577         
     578        if new_class 
     579          # move to another parser class 
     580          @final_parser = new_class.new(nil, :pre_query => self, :elements => elements[i..-1]) 
     581        else 
     582          @distinct ||= elements.size > 1 
     583          @select << "#{table}.*" 
     584 
     585          merge_alternate_queries(@alt_queries) if @alt_queries 
     586 
     587          @limit  = parse_limit_clause(@offset_limit_order_group[:limit]) 
     588          @offset = parse_offset_clause(@offset_limit_order_group[:offset]) 
     589 
     590 
     591          @group = parse_group_clause(@offset_limit_order_group[:group]) 
     592          @order = parse_order_clause(@offset_limit_order_group[:order] || default_order_clause) 
     593        end 
     594      end 
     595 
     596      if @final_parser == self 
     597        after_parse unless @opts[:skip_after_parse] 
     598        @where.compact! 
     599      end 
     600    end 
     601     
     602    def init_with_query(query, opts) 
     603      @opts = opts 
     604       
     605      if query.kind_of?(Array) 
     606        @query = query[0] 
     607        if query.size > 1 
     608          @alt_queries = query[1..-1].map {|q| self.class.new(q, opts.merge(:skip_after_parse => true))} 
     609        end 
     610      else 
     611        @query = query 
     612      end 
     613       
     614       
     615      @offset_limit_order_group = {} 
     616      if @query == nil || @query == '' 
     617        elements = [main_table] 
     618      else 
     619        elements = @query.split(' from ') 
     620        last_element = elements.last 
     621        last_element, @offset_limit_order_group[:offset] = last_element.split(' offset ') 
     622        last_element, @offset_limit_order_group[:limit]  = last_element.split(' limit ') 
     623        last_element, @offset_limit_order_group[:order]  = last_element.split(' order by ') 
     624        elements[-1], @offset_limit_order_group[:group]  = last_element.split(' group by ') 
     625      end 
     626       
     627      @offset_limit_order_group[:limit] = opts[:limit] || @offset_limit_order_group[:limit] 
     628      # In order to know the table names of the dependencies, we need to parse it backwards. 
     629      # We first find the closest elements, then the final ones. For example, "pages from project" we need 
     630      # project information before getting 'pages'.  
     631      @elements = elements.reverse 
     632 
     633      @tables  = [] 
     634      @join_tables = {} 
     635      @table_counter = {} 
     636      @where  = [] 
     637      # list of tables that need to be added for filter clauses (should be added only once per part) 
     638      @needed_tables = {} 
     639      # list of tables that need to be added through a join (should be added only once per part) 
     640      @needed_join_tables = {} 
     641 
     642      @errors  = [] 
     643 
     644      @main_table ||= 'objects' 
     645 
     646      @select  = [] 
     647 
     648      @ignore_warnings = opts[:ignore_warnings] 
     649 
     650      @ref_date = opts[:ref_date] ? "'#{opts[:ref_date]}'" : 'now()' 
     651    end 
     652 
     653    def init_with_pre_query(pre_query, elements) 
     654      pre_query.instance_variables.each do |iv| 
     655        next if iv == '@query' || iv == '@final_parser' 
     656        instance_variable_set(iv, pre_query.instance_variable_get(iv)) 
     657      end 
     658      @just_changed_class = true 
     659      @elements = elements 
     660    end 
     661     
     662    def clause_error(clause, rest, res) 
     663      "invalid clause #{clause.inspect} near \"#{res[-2..-1]}#{rest[0..1]}\"" 
    589664    end 
    590665end 
  • trunk/lib/query_builder/test/query_builder/errors.yml

    r1042 r1121  
    4343bad_equation: 
    4444  src: "objects where event_at > 2006-04-01'" 
    45   res: "invalid clause \"event_at > 2006-04-01'\" near \"'\"" 
     45  res: "invalid clause \"event_at > 2006-04-01'\" near \"01'\"" 
    4646 
    4747bad_plus_plus: 
    4848  src: "objects where 1 + 3 + + 5 > event_at" 
    49   res: "invalid clause \"1 + 3 + + 5 > event_at\" near \"+ 5 > event_at\"" 
     49  res: "invalid clause \"1 + 3 + + 5 > event_at\" near \"+ + \"" 
    5050   
  • trunk/lib/query_builder/test/query_builder/filters.yml

    r1042 r1121  
    2828 
    2929equation_in_filter: 
    30   src: "objects where event_at > DATE + custom_a months" 
     30  src: "objects where event_at > REF_DATE + custom_a months" 
    3131  res: "SELECT objects.* FROM objects WHERE objects.parent_id = ID AND objects.event_at > now() + INTERVAL objects.custom_a MONTH" 
    3232 
  • trunk/lib/query_builder/test/query_builder_test.rb

    r1040 r1121  
    55 
    66class TestQuery < QueryBuilder 
     7  set_main_table 'objects' 
     8  set_main_class 'Object' 
     9   
    710  load_custom_queries File.join(File.dirname(__FILE__), 'custom_queries') 
    811   
     
    2023   
    2124  private 
     25     
    2226    # Root filters (relations that can be solved without a join). Think 'in clause' (in self, in parent). 
    2327    def context_filter_fields(clause, is_last = false) 
     
    6872    end 
    6973 
     74    def parse_change_class(rel, is_last) 
     75      case rel 
     76      when 'users' 
     77        parse_context(default_context_filter, true) if is_last 
     78        add_table('users') 
     79        @where << "#{table('users')}.node_id = #{field_or_param('id')}" 
     80        return TestUserQuery # class change 
     81      else 
     82        return nil 
     83      end 
     84    end 
     85     
    7086    # Filters that need a join 
    7187    def join_relation(rel, context) 
     
    98114end 
    99115 
     116class TestUser 
     117end 
     118 
     119class TestUserQuery < QueryBuilder 
     120  set_main_table 'users' 
     121  set_main_class 'TestUser' 
     122   
     123  # Default sort order 
     124  def default_order_clause 
     125    "name ASC, first_name ASC" 
     126  end 
     127   
     128  def default_context_filter 
     129    'self' 
     130  end 
     131   
     132  def parse_change_class(rel, is_last) 
     133    case rel 
     134    when 'objects' 
     135      parse_context(default_context_filter, true) if is_last 
     136      add_table('objects') 
     137      @where << "#{table('objects')}.id = #{field_or_param('node_id')}" 
     138      return TestUserQuery # class change 
     139    else 
     140      return nil 
     141    end 
     142  end 
     143   
     144  def parse_relation(clause, context) 
     145    return nil 
     146  end 
     147   
     148  def context_filter_fields(clause, is_last = false) 
     149    nil 
     150  end 
     151   
     152  def parse_context(clause, is_last = false) 
     153     
     154    if fields = context_filter_fields(clause, is_last) 
     155      @where << "#{field_or_param(fields[0])} = #{field_or_param(fields[1], table(main_table,-1))}" if fields != :void 
     156    else 
     157      @errors << "invalid context '#{clause}'" 
     158    end 
     159  end 
     160   
     161  # Map a litteral value to be used inside a query 
     162  def map_literal(value) 
     163    value.inspect 
     164  end 
     165   
     166   
     167  # Overwrite this and take car to check for valid fields. 
     168  def map_field(fld, table_name, context = nil) 
     169    if ['id', 'name', 'first_name', 'node_id'].include?(fld) 
     170      "#{table_name}.#{fld}" 
     171    else 
     172      # TODO: error, raise / ignore ? 
     173    end 
     174  end 
     175   
     176  def map_parameter(fld) 
     177    fld.to_s.upcase 
     178  end 
     179end 
     180 
    100181class QueryTest < Test::Unit::TestCase 
    101   yaml_test :basic, :joins, :filters, :errors 
     182  yaml_test 
    102183   
    103184  def parse(value, opts) 
    104185    query = TestQuery.new(value, opts) 
    105     if res = query.to_sql 
    106       return res 
     186     
     187    (query.main_class != Object ? "#{query.main_class.to_s}: " : '') + if res = query.to_sql 
     188      res 
    107189    else 
    108       return query.errors.join(", ") 
     190      query.errors.join(", ") 
    109191    end 
    110192  end 
  • trunk/lib/yaml_test.rb

    r1078 r1121  
    4040        end 
    4141      end 
    42        
    4342       
    4443      class_eval %Q{ 
  • trunk/public/javascripts/zena.js

    r907 r1121  
    55// preview content from another window. 
    66Zena.editor_preview = function(url, element, value) { 
    7         new Ajax.Request(url, {asynchronous:true, evalScripts:true, parameters:{content: value }}); // $F() 
     7  var key = element.name; 
     8  var full_url = url + '&key=' + key.slice(5, key.length - 1); 
     9        new Ajax.Request(full_url, {asynchronous:true, evalScripts:true, parameters:{content: value }}); // $F() 
    810} 
    911 
  • trunk/test/helpers/application_helper_test.rb

    r1084 r1121  
    178178  def test_date_box 
    179179    @node = secure!(Node) { nodes(:status) } 
    180     assert_match %r{div class="date_box".*img src="\/calendar\/iconCalendar.gif".*input id='datef.*' name='node\[updated_at\]' type='text' value='2006-04-11 00:00'}m, date_box('node', 'updated_at') 
     180    assert_match %r{span class="date_box".*img src="\/calendar\/iconCalendar.gif".*input id='datef.*' name='node\[updated_at\]' type='text' value='2006-04-11 00:00'}m, date_box('node', 'updated_at') 
    181181  end 
    182182   
  • trunk/test/helpers/node_query/relations.yml

    r1111 r1121  
    3030  sql: "SELECT nodes.* FROM nodes WHERE nodes.kpath LIKE 'N