sql_logic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,39 @@
1
+ = SqlLogic, generate complex sql sentence for ActiveRecord finder
2
+
3
+ == Inspired from SearchLogic, but lighter and focused in sql sentence generation.
4
+
5
+ == Install
6
+ $ sudo gem install sql_logic
7
+
8
+ == Usage
9
+ require 'rubygems'
10
+ gem "activerecord"
11
+ require 'sql_logic'
12
+
13
+ #table migration
14
+ #create_table :users do |t|
15
+ # t.string :username
16
+ # t.string :email
17
+ # t.string :phone
18
+ # t.string :sex
19
+ # t.timestamps
20
+ #end
21
+
22
+ class User < ActiveRecord::Base; end
23
+
24
+ p User.user_name_like_to_sql("Wayne") # => ["lower(users.user_name) LIKE :users_user_name", {:users_user_name=>"%wayne%"}]
25
+
26
+ sql_hash = User.get_sql_by_hash({:user_name_like=>"Wayne", :id_lt=>100})
27
+ User.all(:conditions=>sql_hash)
28
+
29
+ #For View
30
+ #<%= text_field_tag "user_name_like", params[:user_name_like], :size=>8 -%>
31
+ #<%= select_tag "sex_eq", options_for_select([["", "M", "F"]}, params[:sex_eq]) -%>
32
+
33
+ #For Controller
34
+ User.all(:conditions=> User.get_sql_by_hash(params))
35
+
36
+ Copyright (c) 2011 MIT-LICENSE
37
+ Author : Wayne Deng
38
+ Web : http://blog.waynedeng.com
39
+ Email : wayne.deng.cn(AT).com
@@ -0,0 +1,53 @@
1
+ require 'activerecord'
2
+ require File.join(File.dirname(__FILE__), "sql_logic","sql_array.rb")
3
+ require File.join(File.dirname(__FILE__), "sql_logic","sql_array_param.rb")
4
+ require File.join(File.dirname(__FILE__), "sql_logic","conditions.rb")
5
+ require File.join(File.dirname(__FILE__), "sql_logic","associations.rb")
6
+ require File.join(File.dirname(__FILE__), "sql_logic","hash_extend.rb")
7
+
8
+ module SqlLogic
9
+
10
+ def get_sql(sql_array)
11
+ sql_array = SQLArray.new(sql_array) if sql_array.is_a?(Hash)
12
+ raise ArgumentError.new("Argument Error!") unless sql_array.is_a?(SQLArray)
13
+
14
+ return sql_array.to_sql(self)
15
+ end
16
+
17
+ def get_sql_by_hash(sql_hash, options={})
18
+ options[:skip_blank]||=true
19
+ options[:strip]||=true
20
+ raise ArgumentError.new("Argument Error!") unless sql_hash.is_a?(Hash)
21
+ sql_hash.delete_if { |key, value| value.blank? } if options[:skip_blank]
22
+ sql_hash.each { |key, value| sql_hash[key] = value.strip if value.respond_to?(:strip) } if options[:strip]
23
+ get_sql(sql_hash)
24
+ end
25
+
26
+ def table_name_without_schema
27
+ self.table_name.include?(".") ? self.table_name.split(".").last : self.table_name
28
+ end
29
+
30
+ def get_sql_by_key_value(key, value)
31
+ #key_method = "#{key}".to_sym
32
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
33
+ if key.to_s =~ /^(#{column_names.join("|")})_(#{Conditions::PRIMARY_CONDITIONS.join("|")})$/ or key.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::PRIMARY_CONDITIONS.join("|")})$/
34
+ key_method = "#{key}_to_sql".to_sym
35
+ return self.send(key_method, value)
36
+ #if self.respond_to?(key_method)
37
+ sql = "#{self.table_name_without_schema}.#{key} = :#{key} "
38
+ return [sql, {key_method=>value}]
39
+ #return self.send(key_method, value)
40
+ #else
41
+ # raise ArgumentError.new("Cannnot find #{key} method!")
42
+ #end
43
+ else
44
+ return nil
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ Hash.send(:include, SqlLogic::HashExtend)
51
+ ActiveRecord::Base.extend(SqlLogic)
52
+ ActiveRecord::Base.extend(SqlLogic::Associations)
53
+ ActiveRecord::Base.extend(SqlLogic::Conditions)
@@ -0,0 +1,51 @@
1
+ module SqlLogic
2
+ module Associations
3
+
4
+ def primary_condition_name(name) # :nodoc:
5
+ if result = super
6
+ result
7
+ elsif association_condition?(name)
8
+ name.to_sym
9
+ elsif details = association_alias_condition_details(name)
10
+ "#{details[:association]}_#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
11
+ else
12
+ nil
13
+ end
14
+ end
15
+
16
+ # Is the name of the method a valid name for an association condition?
17
+ def association_condition?(name)
18
+ !association_condition_details(name).nil?
19
+ end
20
+
21
+ # Is the ane of the method a valie name for an association alias condition?
22
+ # An alias being "gt" for "greater_than", etc.
23
+ def association_alias_condition?(name)
24
+ !association_alias_condition_details(name).nil?
25
+ end
26
+
27
+ private
28
+ def method_missing(name, *args, &block)
29
+ if details = association_condition_details(name)
30
+ create_association_condition(details[:association], details[:column], details[:condition], args)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def association_condition_details(name)
37
+ associations = reflect_on_all_associations.collect { |assoc| assoc.name }
38
+ if name.to_s =~ /^(#{associations.join("|")})_(\w+)_(#{Conditions::PRIMARY_CONDITIONS.join("|")})_to_sql$/
39
+ {:association => $1, :column => $2, :condition => $3}
40
+ end
41
+ end
42
+
43
+ def create_association_condition(association_name, column, condition, args)
44
+ #named_scope("#{association_name}_#{column}_#{condition}", association_condition_options(association_name, "#{column}_#{condition}", args))
45
+ values = args
46
+ association = reflect_on_association(association_name.to_sym)
47
+ association_method = "#{column}_#{condition}_to_sql"
48
+ result = association.klass.send(association_method, *args)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,186 @@
1
+ module SqlLogic
2
+ module Conditions
3
+ COMPARISON_CONDITIONS = {
4
+ :equals => [:is, :eq],
5
+ :eq => [],
6
+ :does_not_equal => [:not_equal_to, :is_not, :not, :neq],
7
+ :neq => [],
8
+ :less_than => [:lt, :before],
9
+ :lt => [], :lte=>[],
10
+ :less_than_or_equal_to => [:lte],
11
+ :greater_than => [:gt, :after],
12
+ :gt=>[], :gte=>[],
13
+ :in=>[], :not_in=>[],
14
+ :greater_than_or_equal_to => [:gte],
15
+ }
16
+
17
+ WILDCARD_CONDITIONS = {
18
+ :like => [:contains, :includes],
19
+ :begins_with => [:bw],
20
+ :ends_with => [:ew],
21
+ :full_text=> []
22
+ }
23
+
24
+ BOOLEAN_CONDITIONS = {
25
+ :null => [:nil],
26
+ :empty => []
27
+ }
28
+
29
+ CONDITIONS = {}
30
+
31
+ COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
32
+ CONDITIONS[condition] = aliases
33
+ end
34
+
35
+ BOOLEAN_CONDITIONS.each { |condition, aliases| CONDITIONS[condition] = aliases }
36
+
37
+ PRIMARY_CONDITIONS = CONDITIONS.keys
38
+ ALIAS_CONDITIONS = CONDITIONS.values.flatten
39
+
40
+ # Returns the primary condition for the given alias. Ex:
41
+ #
42
+ # primary_condition(:gt) => :greater_than
43
+ def primary_condition(alias_condition)
44
+ CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
45
+ end
46
+
47
+ def primary_condition_name(name)
48
+ if primary_condition?(name)
49
+
50
+ name.to_sym
51
+ elsif details = alias_condition_details(name)
52
+ "#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def primary_condition?(name)
59
+ !primary_condition_details(name).nil?
60
+ end
61
+
62
+ private
63
+ def method_missing(name, *args, &block)
64
+ if details = primary_condition_details(name)
65
+ get_sql_and_params(details[:column], details[:condition], args)
66
+ else
67
+ super
68
+ end
69
+ end
70
+
71
+ def primary_condition_details(name)
72
+ if name.to_s =~ /^(#{column_names.join("|")})_(#{PRIMARY_CONDITIONS.join("|")})_to_sql$/
73
+ {:column => $1, :condition => $2}
74
+ end
75
+ end
76
+
77
+ def get_sql_and_params(column, condition, args)
78
+ column_type = columns_hash[column.to_s].type
79
+ case condition.to_s
80
+ when /^equals/, /^eq/
81
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} = ?", args)
82
+ when /^does_not_equal/, /^noteq/, /^neq/
83
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} != ?", args)
84
+ when /^less_than_or_equal_to/, /^lte/
85
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} <= ?", args, :lte)
86
+ when /^less_than/, /^lt/
87
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} < ?", args, :lt)
88
+ when /^greater_than_or_equal_to/, /^gte/
89
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} >= ?", args, :gte)
90
+ when /^greater_than/, /^gt/
91
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} > ?", args, :gt)
92
+ when /^like/
93
+ condition_sql(condition, column, column_type, "lower(#{table_name_without_schema}.#{column}) LIKE ?", args, :like)
94
+ when /^begins_with/
95
+ condition_sql(condition, column, column_type, "lower(#{table_name_without_schema}.#{column}) LIKE ?", args, :begins_with)
96
+ when /^ends_with/
97
+ condition_sql(condition, column, column_type, "lower(#{table_name_without_schema}.#{column}) LIKE ?", args, :ends_with)
98
+ when /^in/
99
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} IN (?)", args)
100
+ when /^not_in/
101
+ condition_sql(condition, column, column_type, "#{table_name_without_schema}.#{column} NOT IN (?)", args)
102
+ when /^full_text/
103
+ condition_sql(condition, column, column_type, "CONTAINS(#{table_name_without_schema}.#{column}, ?) > 0", args)
104
+ when "null"
105
+ return ["#{table_name_without_schema}.#{column} IS NULL", {}]
106
+ when "empty"
107
+ return ["#{table_name_without_schema}.#{column} = ''", {}]
108
+ end
109
+ end
110
+
111
+ def condition_sql(condition, column, column_type, sql, args, value_modifier = nil)
112
+ case condition.to_s
113
+ when /_(any|all)$/
114
+ #TODO
115
+ values = args
116
+ return ["", {}] if values.empty?
117
+ values = values.flatten
118
+
119
+ values_to_sub = nil
120
+ if value_modifier.nil?
121
+ values_to_sub = values
122
+ else
123
+ values_to_sub = values.collect { |value| value_with_modifier(value, value_modifier, column_type) }
124
+ end
125
+
126
+ join = $1 == "any" ? " OR " : " AND "
127
+ _sql = [values.collect { |value| sql }.join(join), *values_to_sub]
128
+ else
129
+ value = args[0]
130
+ column_symbol = column_key_symbol(column)
131
+ _sql = sql.gsub("?", column_symbol_in_sql(column_symbol, column_type))
132
+ return [_sql, {column_symbol.to_sym=>value_with_modifier(value, value_modifier, column_type)}]
133
+ end
134
+ end
135
+
136
+ def column_key_symbol(column)
137
+ [self.table_name_without_schema, column].join("_")
138
+ end
139
+
140
+ def column_symbol_in_sql(column, column_type)
141
+ case column_type
142
+ when :datetime
143
+ "to_date1(:#{column})"
144
+ else
145
+ ":#{column}"
146
+ end
147
+ end
148
+
149
+ def value_with_modifier(value, modifier, column_type)
150
+ case column_type
151
+ when :datetime
152
+ if [:gt, :gte].include?(modifier)
153
+ return fill_date(value, '00:00:00')
154
+ elsif [:lt, :lte].include?(modifier)
155
+ return fill_date(value, '23:59:59')
156
+ end
157
+ end
158
+ case modifier
159
+ when :like
160
+ "%#{value.downcase}%"
161
+ when :begins_with
162
+ "#{value.downcase}%"
163
+ when :ends_with
164
+ "%#{value.downcase}"
165
+ else
166
+ value
167
+ end
168
+ end
169
+
170
+ def fill_date(date, time)
171
+ if date.is_a?(String)
172
+ if date.size==10
173
+ new_date = Time.parse([date, time].join(" "))
174
+ return new_date.label
175
+ else
176
+ new_date = Time.parse(date)
177
+ return new_date.label
178
+ end
179
+ elsif date.is_a?(Time)
180
+ return date.label
181
+ else
182
+ return date.to_s
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,42 @@
1
+ module SqlLogic
2
+ module HashExtend
3
+
4
+ def +(h)
5
+ return SQLArray.new([self, "AND", h])
6
+ end
7
+
8
+ def -(h)
9
+ return SQLArray.new([self, "OR", h])
10
+ end
11
+
12
+ def delete_blank!
13
+ self.delete_if { |key, value| value.blank? }
14
+ end
15
+
16
+ def to_s
17
+ return self.inspect
18
+ end
19
+
20
+ def to_sql(record=nil)
21
+ sql_with_params = []
22
+ self.each do |key, value|
23
+ if record
24
+ sql_p = record.get_sql_by_key_value(key, value)
25
+ sql_with_params << sql_p if sql_p
26
+ else
27
+ sql_with_params << ["#{key} = #{value}", {}]
28
+ end
29
+ end
30
+ sql_array = SQLArrayParam.new
31
+ sql_with_params.each do |sp|
32
+ sql_array.add(sp)
33
+ end
34
+ if sql_with_params.size>1
35
+ sql = ["(", sql_array.sql, ")"].join
36
+ else
37
+ sql = sql_array.sql
38
+ end
39
+ return [sql, sql_array.sql_params]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ module SqlLogic
2
+ class SQLArray
3
+ def initialize(sql)
4
+ if sql.is_a?(Hash)
5
+ @array = [sql, "AND", nil]
6
+ elsif sql.is_a?(Array)
7
+ @array = sql
8
+ end
9
+ end
10
+
11
+ def to_s
12
+ if @array[2]==nil
13
+ @array[0]
14
+ else
15
+ if @array[0].blank?
16
+ @array[2]
17
+ else
18
+ ["(", @array.join(" "), ")"].join
19
+ end
20
+ end
21
+ end
22
+
23
+ def to_sql(record=nil)
24
+ if @array[2]==nil
25
+ @array[0].to_sql(record)
26
+ else
27
+ sql_array = SQLArrayParam.new
28
+ left_side, right_side = [@array[0].to_sql(record), @array[2].to_sql(record)]
29
+ sql_array.merge!(left_side, right_side, @array[1])
30
+ return sql_array.to_a
31
+ end
32
+ end
33
+
34
+ def +(h)
35
+ if h.is_a?(Hash)
36
+ return SQLArray.new([self, "AND", h])
37
+ elsif h.is_a?(SQLArray)
38
+ return SQLArray.new([self, "AND", h])
39
+ end
40
+ end
41
+
42
+ def -(h)
43
+ if h.is_a?(Hash)
44
+ return SQLArray.new([self, "OR", h])
45
+ elsif h.is_a?(SQLArray)
46
+ return SQLArray.new([self, "OR", h])
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,73 @@
1
+ module SqlLogic
2
+ class SQLArrayParam
3
+ def initialize(param = nil)
4
+ @param_ary ||= ["", {}]
5
+ @param_ary = param.to_a if param
6
+ end
7
+
8
+ def to_a
9
+ @param_ary
10
+ end
11
+
12
+ def sql
13
+ @param_ary[0]
14
+ end
15
+
16
+ def sql=(value)
17
+ @param_ary[0]=value
18
+ end
19
+
20
+ def sql_params
21
+ @param_ary[1]
22
+ end
23
+
24
+ def sql_params=(value)
25
+ @param_ary[1]=value
26
+ end
27
+
28
+ def add(param, connector = "AND")
29
+ data = pre_add(param)
30
+ self.sql+=((self.sql.blank? ? "" : " #{connector} ") + data.sql)
31
+ self.sql_params.merge!(data.sql_params)
32
+ return self
33
+ end
34
+
35
+ def pre_add(param)
36
+ param = SQLArrayParam.new(param) if param.is_a?(Array)
37
+ new_sql = param.sql
38
+ new_param = {}
39
+ param.sql_params.each do |key, value|
40
+ new_key = valid_key(key)
41
+ if new_key!=key
42
+ new_sql.gsub!(Regexp.new(":#{key}\\b"), ":#{new_key}")
43
+ end
44
+ new_param.merge!({new_key=>value})
45
+ end
46
+ return SQLArrayParam.new([new_sql, new_param])
47
+ end
48
+
49
+ def merge!(param, param1, connector = "AND")
50
+ param = SQLArrayParam.new(param) if param.is_a?(Array)
51
+ param1 = SQLArrayParam.new(param1) if param1.is_a?(Array)
52
+ data = pre_add(param)
53
+ self.sql_params.merge!(data.sql_params)
54
+ data1 = pre_add(param1)
55
+ self.sql_params.merge!(data1.sql_params)
56
+ if data.sql.blank?
57
+ self.sql += data1.sql
58
+ else
59
+ self.sql += ["(", data.sql, " #{connector} ", data1.sql, ")"].join
60
+ end
61
+
62
+ return true
63
+ end
64
+
65
+ def valid_key(key, i=0)
66
+ if self.sql_params.keys.include?(key)
67
+ return valid_key("#{key}_#{i}".to_sym, i+1)
68
+ else
69
+ return key
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{sql_logic}
3
+ s.version = "0.0.1"
4
+
5
+ s.authors = ["Wayne Deng"]
6
+ s.date = %q{2011-11-04}
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "generate complex sql sentences for ActiveRecord finder"
9
+ s.description = "Inspired from SearchLogic, but lighter and focused in sql sentence generation."
10
+ s.email = %q{wayne.deng.cn@gmail.com}
11
+ s.homepage = %q{http://blog.waynedeng.com}
12
+ s.files = ["test/test_sql_logic.rb", "test/test.db", "README", "lib/sql_logic.rb", "lib/sql_logic/associations.rb", "lib/sql_logic/conditions.rb", "lib/sql_logic/hash_extend.rb", "lib/sql_logic/sql_array.rb", "lib/sql_logic/sql_array_param.rb", "sql_logic.gemspec"]
13
+ s.require_paths = ["lib"]
14
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "SqlLogic", "--main", "README"]
15
+
16
+ end
Binary file
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "rubygems"
4
+ gem "activerecord"
5
+
6
+ require File.join(File.dirname(__FILE__),"..","lib","sql_logic.rb")
7
+ require 'test/unit'
8
+
9
+ ActiveRecord::Base.establish_connection(
10
+ :adapter => 'sqlite3',
11
+ :database => 'test.db',
12
+ :pool => 5,
13
+ :timeout=> 5000
14
+ )
15
+
16
+ class User < ActiveRecord::Base; end
17
+
18
+ class TestSqlLogic < Test::Unit::TestCase
19
+
20
+ def test_magic_method
21
+ assert_equal User.user_name_like_to_sql("Wayne"), ["lower(users.user_name) LIKE :users_user_name",
22
+ {:users_user_name=>"%wayne%"}]
23
+
24
+ assert_equal User.id_gt_to_sql(100), ["users.id > :users_id", {:users_id=>100}]
25
+ end
26
+
27
+ def test_get_sql_by_hash
28
+ assert_equal User.get_sql_by_hash({:user_name_like=>"Wayne", :id_lt=>100}),
29
+ ["(lower(users.user_name) LIKE :users_user_name AND users.id < :users_id)",
30
+ {:users_user_name=>"%wayne%", :users_id=>100}]
31
+ end
32
+
33
+ def test_get_sql
34
+ assert_equal User.get_sql({:user_name_like=>"Wayne", :id_lt=>100} - {:id_gte=>100}),
35
+ ["((lower(users.user_name) LIKE :users_user_name AND users.id < :users_id) OR users.id >= :users_id_0)",
36
+ {:users_user_name=>"%wayne%", :users_id=>100, :users_id_0=>100}]
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sql_logic
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Wayne Deng
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-11-04 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Inspired from SearchLogic, but lighter and focused in sql sentence generation.
23
+ email: wayne.deng.cn@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - test/test_sql_logic.rb
32
+ - test/test.db
33
+ - README
34
+ - lib/sql_logic.rb
35
+ - lib/sql_logic/associations.rb
36
+ - lib/sql_logic/conditions.rb
37
+ - lib/sql_logic/hash_extend.rb
38
+ - lib/sql_logic/sql_array.rb
39
+ - lib/sql_logic/sql_array_param.rb
40
+ - sql_logic.gemspec
41
+ has_rdoc: true
42
+ homepage: http://blog.waynedeng.com
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --line-numbers
48
+ - --inline-source
49
+ - --title
50
+ - SqlLogic
51
+ - --main
52
+ - README
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 3
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.5.2
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: generate complex sql sentences for ActiveRecord finder
80
+ test_files: []
81
+