sql_logic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+