dolzenko 0.0.23 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ <div>
2
+ <p>
3
+ Recently I was browsing
4
+ <a href="http://docs.djangoproject.com/en/1.2/">
5
+ Django</a>
6
+ documentation deliberately looking for
7
+ some ideas to cross-pollinate to Rails. F() and Q() objects looked like
8
+ nice little hacks so I decided to implement them for Rails.
9
+ </p>
10
+
11
+ <!--more-->
12
+
13
+ <h2>Q() Object</h2>
14
+
15
+ <p>
16
+ Even though it's not nearly as powerful as his Star Trek namesake
17
+ it's still pretty interesting to look at (original docs available
18
+ <a href="http://docs.djangoproject.com/en/dev/topics/db/queries/#complex-lookups-with-q-objects">
19
+ here</a>).
20
+ It tackles the problem of complex SQL conditions building.
21
+ Usually most issues arise from the fact that simple hash based conditions
22
+ don't allow for negated or <code>OR</code>ed conditions. There are plenty of
23
+ other projects aimed to mitigate these restrictions among others (a while
24
+ ago I assembled the collection of these):
25
+ <a href="http://github.com/thoughtbot/squirrel">Squirrel</a>,
26
+ <a href="http://github.com/defunkt/ambition">Ambition</a>,
27
+ <a href="http://github.com/knowtheory/dm-sugar-glider">dm-sugar-glider</a>,
28
+ <a href="http://github.com/ezmobius/ez-where">ez_where</a>,
29
+ <a href="http://github.com/johnbender/rquery">RQuery</a>,
30
+ <a href="http://github.com/binarylogic/searchlogic">Searchlogic</a>,
31
+ <a href="http://github.com/jeremyevans/sequel">Sequel</a>,
32
+ probably most recent one is
33
+ <a href="http://github.com/ernie/meta_where">MetaWhere</a>.
34
+ </p>
35
+
36
+ <p style="clear: left;">
37
+ There are different tricks above libraries employ
38
+ <ol>
39
+ <li>
40
+ <code>instance_eval</code> and/or <code>method_missing</code> letting you
41
+ write
42
+ [ruby]
43
+ # Squirrel
44
+ Playlist.find(:all) do
45
+ any do
46
+ name == "Party Mix"
47
+ total_length > 3600
48
+ end
49
+ end
50
+ [/ruby]
51
+ or
52
+ [ruby]
53
+ # RQuery
54
+ User.where do |user|
55
+ (user.age > 20) | (user.age.in 16,18)
56
+ end
57
+ [/ruby]
58
+ </li>
59
+ <li>core objects extensions
60
+ (extending <code>Hash</code> and/or <code>Symbol</code> objects)
61
+ [ruby]
62
+ # Sequel
63
+ Artist.filter(:name.like('Y%') & ({:b=>1} | ~{:c=>3}))
64
+ # SELECT * FROM artists WHERE name LIKE 'Y%' AND (b = 1 OR c != 3)
65
+ [/ruby]
66
+ </li>
67
+ <li>
68
+ hooking the interpreter (accessing syntax tree of the condition
69
+ specifying block)
70
+ [ruby]
71
+ # Ambition
72
+ >> SQL::User.select { |m| m.name == 'jon' && m.age == 21 }.to_s
73
+ => "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 21"
74
+ [/ruby]
75
+ </li>
76
+ </ol>
77
+ </p>
78
+
79
+ <p>
80
+ While there is nothing technically wrong with the approaches listed above,
81
+ it's interesting to see what can be done without resorting to any of these
82
+ tricks. The Q() object fits there very well. It let's you build ORed,
83
+ ANDed, and negated conditions without dropping to writing SQL (drawing
84
+ on simple and well known hash-based syntax). Below is the illustration
85
+ of Q() based syntax and the corresponding SQL fragments generated by it
86
+
87
+ [ruby]
88
+ ~Q(:user_id => nil) # => user_id IS NOT NULL
89
+
90
+ Q(:user_id => nil) | Q(:id => nil) # => user_id IS NULL OR id IS NULL
91
+
92
+ ~(Q(:user_id => nil) & Q(:id => nil)) # => user_id IS NOT NULL AND id IS NOT NULL
93
+ [/ruby]
94
+
95
+ Ruby 1.9 allows <code>!</code> operator overloading leading to more
96
+ natural syntax for negated conditions
97
+
98
+ [ruby]
99
+ !Q(:user_id => nil) # => user_id IS NOT NULL
100
+ [/ruby]
101
+ </p>
102
+
103
+ <h2>F() Object</h2>
104
+
105
+ <p>
106
+ <a href="http://docs.djangoproject.com/en/dev/topics/db/queries/#query-expressions">
107
+ Original docs</a>. It let's you reference your database columns and do
108
+ simple calculations on them from your conditions and update statements.
109
+ The concept is pretty simple and can be illustrated with a few
110
+ examples
111
+ [ruby]
112
+ # Simple column reference
113
+ User.where(:updated_at => F(:created_at))
114
+ # instead of writing it in SQL like: User.where("updated_at = created_at")
115
+
116
+ # Column reference with calculation
117
+ User.where(:updated_at => F(:created_at) + 1)
118
+ # instead of User.where("updated_at = created_at + 1")
119
+
120
+ # Calculation with references also work
121
+ User.where(:updated_at => F(:created_at) + F(:updated_at))
122
+ # instead of User.where("updated_at = created_at + updated_at")
123
+
124
+ # Use in update statement
125
+ User.update_all(:id_copy => F(:id))
126
+ # instead of User.update_all("id_copy = id")
127
+ [/ruby]
128
+
129
+ Nice side effect of using this is that you get atomic operations for free
130
+ [ruby]
131
+ user = User.find(1)
132
+ user.views = F(:views) + 1 #
133
+ user.save
134
+ [/ruby]
135
+
136
+ Whenever the <code>save</code> is called it's the current `views`
137
+ that will be incremented (not the value that that was loaded with
138
+ <code>User.find</code>).
139
+ </p>
140
+
141
+ <h2>Installation</h2>
142
+
143
+ <p>
144
+ You can get the code from my eponymous gem like this
145
+ [ruby]
146
+ > gem install dolzenko
147
+ > irb
148
+
149
+ [/ruby]
150
+ </p>
151
+
152
+ </div>
153
+
@@ -28,7 +28,7 @@
28
28
  #
29
29
  #
30
30
 
31
- require "require_gist"; require_gist "383954/ea5a41269aac073b596b21fe392098827186a32b/alias_method_chain_once.rb", "a6f068593bb45fe6c9956205f672ac4a0c2e1671" # http://gist.github.com/383954
31
+ require "dolzenko/alias_method_chain_once.rb"
32
32
 
33
33
  class F
34
34
  attr_accessor :attr_name
@@ -21,7 +21,7 @@
21
21
  # User.where(!Q(:user_id => nil))
22
22
  #
23
23
 
24
- require "require_gist"; require_gist "383954/ea5a41269aac073b596b21fe392098827186a32b/alias_method_chain_once.rb", "a6f068593bb45fe6c9956205f672ac4a0c2e1671" # http://gist.github.com/383954
24
+ require "dolzenko/alias_method_chain_once"
25
25
 
26
26
  class Q
27
27
  def |(other)
@@ -0,0 +1,121 @@
1
+ <div>
2
+ <h2>Age-Old Problem</h2>
3
+
4
+ <p>
5
+ Suppose you want to authenticate user from the login/password entered
6
+ on the web from
7
+ [ruby]
8
+ user = User.first(:conditions => "login = '#{ login }' AND password = '#{ password }'")
9
+ [/ruby]
10
+ Such authentication code as we know can be easily fooled with the
11
+ <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL injection</a>.
12
+
13
+ Thankfully in Rails there are parameterized queries and the above can be
14
+ rewritten in at least 3 injection safe ways
15
+ [ruby]
16
+ User.find_by_login_and_password(login, password)
17
+ # or
18
+ User.first(:conditions => ["login = ? AND password = ?", login, password])
19
+ # or
20
+ User.first(:conditions => ["login = :login AND password = :password", { :login => login, :password => password }])
21
+ [/ruby]
22
+ </p>
23
+
24
+ <h2>Many Solutions (Haskell Included)</h2>
25
+
26
+ <p>
27
+ Parameterized queries cover most of the cases but what if you have large
28
+ query with multiple parameters?
29
+ You can quickly lose track of all the question marks and order
30
+ in which parameters appear. Most likely you will prefer the latter,
31
+ hash-based notation.
32
+ </p>
33
+
34
+ <!--more-->
35
+
36
+ <p>
37
+ Side note: This stuff is actually analyzed in depth by
38
+ <a href="http://onestepback.org/">
39
+ Jim Weirich</a>
40
+ in his
41
+ <a href="http://mwrc2009.confreaks.com/14-mar-2009-18-10-the-building-blocks-of-modularity-jim-weirich.html">
42
+ The Building Blocks of Modularity</a>
43
+ presentation where he talks about the connascence in software.
44
+ </p>
45
+
46
+ <p>
47
+ Back to our topic what we really want is just to write SQL queries
48
+ injecting properly quoted values where needed. In other words we would like
49
+ to be able to customize the way string interpolation works. In cool
50
+ languages like Haskell this concept is natively supported and called
51
+ <a href="http://www.haskell.org/haskellwiki/Quasiquotation">
52
+ Quasiquotation</a>.
53
+ While not really on par with such rigorous achievements it can
54
+ be simulated everywhere where <code>eval</code> is available.
55
+ Here is for example
56
+ <a href="http://google-caja.googlecode.com/svn/changes/mikesamuel/string-interpolation-29-Jan-2008/trunk/src/js/com/google/caja/interp/index.html">
57
+ the solution</a>
58
+ from the
59
+ <a href="http://en.wikipedia.org/wiki/Caja_(programming_language)">
60
+ Google Caja</a>
61
+ team.
62
+ </p>
63
+
64
+ <h2>Hack For Better Future</h2>
65
+
66
+ <p>
67
+ There is <code>eval</code> in Ruby and there is also one peculiar feature
68
+ about it. You can pass <code>binding</code> to it, and when the
69
+ block is created it's <code>binding</code> is captured and therefore can be
70
+ used from anywhere (providing access to the lexical scope captured by the
71
+ block). Let's illustrate that
72
+ [ruby]
73
+ ruby-head > def m
74
+ local = 42
75
+ proc {} # return empty block to get binding from it later
76
+ end
77
+ ruby-head > eval("local", m.binding) # binding gives access to the scope where block was created
78
+ => 42
79
+ [/ruby]
80
+ </p>
81
+
82
+ <p>
83
+ Using that feature we can hide <code>eval</code> and implement our own
84
+ safe interpolation routines like I did in
85
+ <a href="http://github.com/dolzenko/dolzenko-gem/blob/master/lib/dolzenko/safe_interpolate.rb">
86
+ safe_interpolate.rb</a>.
87
+ Use it like
88
+
89
+ [ruby]
90
+ ruby-head > require 'dolzenko/safe_interpolate'
91
+ ruby-head > include SafeInterpolate
92
+ ruby-head > login = "' or 1=1"
93
+ ruby-head > sql_interpolate { 'login = #{ login }' }
94
+ => "login = ''' or 1=1'" # single quote got turned into double and the whole expression is quoted itself
95
+ [/ruby]
96
+
97
+ HTML and URI escaping are also provided
98
+ [ruby]
99
+ ruby-head > content = "<script>alert(1)</script>"
100
+ ruby-head > html_interpolate { '<p>#{ content }</p>' }
101
+ => "<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>" # injection prevented!
102
+
103
+ ruby-head > query = "&admin=1"
104
+ ruby-head > uri_interpolate { 'http://example.com?q=#{ query }' }
105
+ => "http://example.com?q=%26admin%3D1"
106
+ [/ruby]
107
+ </p>
108
+
109
+ <h2>Further Reading</h2>
110
+
111
+ <p>
112
+ <a href="http://blog.moertel.com/articles/2006/10/18/a-type-based-solution-to-the-strings-problem">
113
+ A type-based solution to the "strings problem": a fitting end to XSS and SQL-injection holes?</a>
114
+ by Tom Moertel. This article got me interested in the subject. <br>
115
+
116
+ <a href="http://intoverflow.wordpress.com/2010/06/30/haskell-features-id-like-to-see-in-other-languages/">
117
+ Haskell features I'd like to see in other languages</a>
118
+ by Tim Carstens. Check the &laquo;Quasi-quoting&raquo; section for collection of Haskell
119
+ quoting related links.
120
+ </p>
121
+ </div>
@@ -2,8 +2,10 @@ require "active_support/all"
2
2
  require "active_record"
3
3
  require "cgi"
4
4
 
5
+ # http://dolzhenko.org/blog/2010/07/safe-string-interpolation-in-ruby/
5
6
  module SafeInterpolate
6
7
  def generic_interpolate(string_block, interpolator)
8
+ raise ArgumentError, "block returning string to interpolate must be provided" unless string_block
7
9
  string_with_interpolations = string_block.call
8
10
  string_with_interpolations.gsub(/\#\{([^}]*)\}/) do
9
11
  result = eval($1, string_block.binding)
@@ -11,6 +13,11 @@ module SafeInterpolate
11
13
  end
12
14
  end
13
15
 
16
+ # Examples
17
+ #
18
+ # include SafeInterpolate
19
+ # ...
20
+ # sql_interpolate { 'name = #{ name }' } # => "name = 'Bob'"
14
21
  def sql_interpolate(&string_block)
15
22
  generic_interpolate(string_block, ActiveRecord::Base.connection.method(:quote))
16
23
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 23
9
- version: 0.0.23
8
+ - 24
9
+ version: 0.0.24
10
10
  platform: ruby
11
11
  authors:
12
12
  - Evgeniy Dolzhenko
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-07-02 00:00:00 +04:00
17
+ date: 2010-07-03 00:00:00 +04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -63,6 +63,7 @@ files:
63
63
  - lib/dolzenko/core_ext/kernel/in.rb
64
64
  - lib/dolzenko/core_ext/kernel/r.rb
65
65
  - lib/dolzenko/core_ext/object/is_an.rb
66
+ - lib/dolzenko/django_f_object.html
66
67
  - lib/dolzenko/django_f_object.rb
67
68
  - lib/dolzenko/django_q_object.rb
68
69
  - lib/dolzenko/error_print.rb
@@ -72,6 +73,7 @@ files:
72
73
  - lib/dolzenko/io_interceptor.rb
73
74
  - lib/dolzenko/light.rb
74
75
  - lib/dolzenko/remote_download.rb
76
+ - lib/dolzenko/safe_interpolate.html
75
77
  - lib/dolzenko/safe_interpolate.rb
76
78
  - lib/dolzenko/shell_out.rb
77
79
  - lib/dolzenko/try_block.rb