diff options
-rw-r--r-- | History.txt | 2 | ||||
-rw-r--r-- | README.markdown | 17 | ||||
-rw-r--r-- | arel.gemspec | 6 | ||||
-rw-r--r-- | lib/arel.rb | 1 | ||||
-rw-r--r-- | lib/arel/attributes/attribute.rb | 1 | ||||
-rw-r--r-- | lib/arel/math.rb | 19 | ||||
-rw-r--r-- | lib/arel/nodes.rb | 1 | ||||
-rw-r--r-- | lib/arel/nodes/math_operation.rb | 15 | ||||
-rw-r--r-- | lib/arel/visitors/to_sql.rb | 16 | ||||
-rw-r--r-- | test/support/fake_record.rb | 14 | ||||
-rw-r--r-- | test/visitors/test_to_sql.rb | 22 |
11 files changed, 107 insertions, 7 deletions
diff --git a/History.txt b/History.txt index fac98381a6..a5f630b021 100644 --- a/History.txt +++ b/History.txt @@ -9,6 +9,8 @@ * Add Arel::SelectManager#limit= * Add Arel::SelectManager#offset * Add Arel::SelectManager#offset= + * Math operations have been added to attributes, thanks to + Vladimir Meremyanin. * Bug fixes diff --git a/README.markdown b/README.markdown index b71879e4e3..4952ec6191 100644 --- a/README.markdown +++ b/README.markdown @@ -74,6 +74,23 @@ The `AND` operator behaves similarly. The examples above are fairly simple and other libraries match or come close to matching the expressiveness of Arel (e.g., `Sequel` in Ruby). +#### Inline math operations + +Suppose we have a table `products` with prices in different currencies. And we have a table currency_rates, of constantly changing currency rates. In Arel: + + products = Arel::Table.new(:products) + products.columns # => [products[:id], products[:name], products[:price], products[:currency_id]] + + currency_rates = Arel::Table.new(:currency_rates) + currency_rates.columns # => [currency_rates[:from_id], currency_rates[:to_id], currency_rates[:date], currency_rates[:rate]] + +Now, to order products by price in user preferred currency simply call: + + products. + join(:currency_rates).on(products[:currency_id].eq(currency_rates[:from_id])). + where(currency_rates[:to_id].eq(user_preferred_currency), currency_rates[:date].eq(Date.today)). + order(products[:price] * currency_rates[:rate]) + #### Complex Joins Where Arel really shines in its ability to handle complex joins and aggregations. As a first example, let's consider an "adjacency list", a tree represented in a table. Suppose we have a table `comments`, representing a threaded discussion: diff --git a/arel.gemspec b/arel.gemspec index 16ceb11c05..58cb8f61fb 100644 --- a/arel.gemspec +++ b/arel.gemspec @@ -2,15 +2,15 @@ Gem::Specification.new do |s| s.name = %q{arel} - s.version = "2.0.7.beta.20110224095102" + s.version = "2.0.7.beta.20110228092631" s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= s.authors = ["Aaron Patterson", "Bryan Halmkamp", "Emilio Tagua", "Nick Kallen"] - s.date = %q{2011-02-24} + s.date = %q{2011-02-28} s.description = %q{Arel is a Relational Algebra for Ruby. It 1) simplifies the generation complex of SQL queries and it 2) adapts to various RDBMS systems. It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on innovative object and collection modeling as opposed to database compatibility and query generation.} s.email = ["aaron@tenderlovemaking.com", "bryan@brynary.com", "miloops@gmail.com", "nick@example.org"] s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown"] - s.files = [".autotest", "History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown", "Rakefile", "arel.gemspec", "lib/arel.rb", "lib/arel/attributes.rb", "lib/arel/attributes/attribute.rb", "lib/arel/compatibility/wheres.rb", "lib/arel/crud.rb", "lib/arel/delete_manager.rb", "lib/arel/deprecated.rb", "lib/arel/expression.rb", "lib/arel/expressions.rb", "lib/arel/factory_methods.rb", "lib/arel/insert_manager.rb", "lib/arel/nodes.rb", "lib/arel/nodes/and.rb", "lib/arel/nodes/binary.rb", "lib/arel/nodes/count.rb", "lib/arel/nodes/delete_statement.rb", "lib/arel/nodes/equality.rb", "lib/arel/nodes/function.rb", "lib/arel/nodes/in.rb", "lib/arel/nodes/inner_join.rb", "lib/arel/nodes/insert_statement.rb", "lib/arel/nodes/join_source.rb", "lib/arel/nodes/named_function.rb", "lib/arel/nodes/node.rb", "lib/arel/nodes/ordering.rb", "lib/arel/nodes/outer_join.rb", "lib/arel/nodes/select_core.rb", "lib/arel/nodes/select_statement.rb", "lib/arel/nodes/sql_literal.rb", "lib/arel/nodes/string_join.rb", "lib/arel/nodes/table_alias.rb", "lib/arel/nodes/unary.rb", "lib/arel/nodes/unqualified_column.rb", "lib/arel/nodes/update_statement.rb", "lib/arel/nodes/values.rb", "lib/arel/nodes/with.rb", "lib/arel/predications.rb", "lib/arel/relation.rb", "lib/arel/select_manager.rb", "lib/arel/sql/engine.rb", "lib/arel/sql_literal.rb", "lib/arel/table.rb", "lib/arel/tree_manager.rb", "lib/arel/update_manager.rb", "lib/arel/visitors.rb", "lib/arel/visitors/depth_first.rb", "lib/arel/visitors/dot.rb", "lib/arel/visitors/join_sql.rb", "lib/arel/visitors/mssql.rb", "lib/arel/visitors/mysql.rb", "lib/arel/visitors/oracle.rb", "lib/arel/visitors/order_clauses.rb", "lib/arel/visitors/postgresql.rb", "lib/arel/visitors/sqlite.rb", "lib/arel/visitors/to_sql.rb", "lib/arel/visitors/visitor.rb", "lib/arel/visitors/where_sql.rb", "test/attributes/test_attribute.rb", "test/helper.rb", "test/nodes/test_as.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_equality.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_update_statement.rb", "test/support/fake_record.rb", "test/test_activerecord_compat.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dot.rb", "test/visitors/test_join_sql.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb", ".gemtest"] + s.files = [".autotest", "History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.markdown", "Rakefile", "arel.gemspec", "lib/arel.rb", "lib/arel/attributes.rb", "lib/arel/attributes/attribute.rb", "lib/arel/compatibility/wheres.rb", "lib/arel/crud.rb", "lib/arel/delete_manager.rb", "lib/arel/deprecated.rb", "lib/arel/expression.rb", "lib/arel/expressions.rb", "lib/arel/factory_methods.rb", "lib/arel/insert_manager.rb", "lib/arel/nodes.rb", "lib/arel/nodes/and.rb", "lib/arel/nodes/binary.rb", "lib/arel/nodes/count.rb", "lib/arel/nodes/delete_statement.rb", "lib/arel/nodes/equality.rb", "lib/arel/nodes/function.rb", "lib/arel/nodes/in.rb", "lib/arel/nodes/inner_join.rb", "lib/arel/nodes/insert_statement.rb", "lib/arel/nodes/join_source.rb", "lib/arel/nodes/named_function.rb", "lib/arel/nodes/node.rb", "lib/arel/nodes/ordering.rb", "lib/arel/nodes/outer_join.rb", "lib/arel/nodes/select_core.rb", "lib/arel/nodes/select_statement.rb", "lib/arel/nodes/sql_literal.rb", "lib/arel/nodes/string_join.rb", "lib/arel/nodes/table_alias.rb", "lib/arel/nodes/unary.rb", "lib/arel/nodes/unqualified_column.rb", "lib/arel/nodes/update_statement.rb", "lib/arel/nodes/values.rb", "lib/arel/nodes/with.rb", "lib/arel/predications.rb", "lib/arel/relation.rb", "lib/arel/select_manager.rb", "lib/arel/sql/engine.rb", "lib/arel/sql_literal.rb", "lib/arel/table.rb", "lib/arel/tree_manager.rb", "lib/arel/update_manager.rb", "lib/arel/visitors.rb", "lib/arel/visitors/depth_first.rb", "lib/arel/visitors/dot.rb", "lib/arel/visitors/join_sql.rb", "lib/arel/visitors/mssql.rb", "lib/arel/visitors/mysql.rb", "lib/arel/visitors/oracle.rb", "lib/arel/visitors/order_clauses.rb", "lib/arel/visitors/postgresql.rb", "lib/arel/visitors/sqlite.rb", "lib/arel/visitors/to_sql.rb", "lib/arel/visitors/visitor.rb", "lib/arel/visitors/where_sql.rb", "test/attributes/test_attribute.rb", "test/helper.rb", "test/nodes/test_as.rb", "test/nodes/test_count.rb", "test/nodes/test_delete_statement.rb", "test/nodes/test_equality.rb", "test/nodes/test_insert_statement.rb", "test/nodes/test_named_function.rb", "test/nodes/test_node.rb", "test/nodes/test_not.rb", "test/nodes/test_or.rb", "test/nodes/test_select_core.rb", "test/nodes/test_select_statement.rb", "test/nodes/test_sql_literal.rb", "test/nodes/test_sum.rb", "test/nodes/test_update_statement.rb", "test/support/fake_record.rb", "test/test_activerecord_compat.rb", "test/test_attributes.rb", "test/test_crud.rb", "test/test_delete_manager.rb", "test/test_factory_methods.rb", "test/test_insert_manager.rb", "test/test_select_manager.rb", "test/test_table.rb", "test/test_update_manager.rb", "test/visitors/test_depth_first.rb", "test/visitors/test_dot.rb", "test/visitors/test_join_sql.rb", "test/visitors/test_mssql.rb", "test/visitors/test_mysql.rb", "test/visitors/test_oracle.rb", "test/visitors/test_postgres.rb", "test/visitors/test_sqlite.rb", "test/visitors/test_to_sql.rb"] s.homepage = %q{http://github.com/rails/arel} s.rdoc_options = ["--main", "README.markdown"] s.require_paths = ["lib"] diff --git a/lib/arel.rb b/lib/arel.rb index 32fc8d9bc0..de429f532e 100644 --- a/lib/arel.rb +++ b/lib/arel.rb @@ -3,6 +3,7 @@ require 'arel/factory_methods' require 'arel/expressions' require 'arel/predications' +require 'arel/math' require 'arel/table' require 'arel/attributes' require 'arel/compatibility/wheres' diff --git a/lib/arel/attributes/attribute.rb b/lib/arel/attributes/attribute.rb index 9a42e5a4da..5aea87ac43 100644 --- a/lib/arel/attributes/attribute.rb +++ b/lib/arel/attributes/attribute.rb @@ -3,6 +3,7 @@ module Arel class Attribute < Struct.new :relation, :name include Arel::Expressions include Arel::Predications + include Arel::Math end class String < Attribute; end diff --git a/lib/arel/math.rb b/lib/arel/math.rb new file mode 100644 index 0000000000..b7c2419233 --- /dev/null +++ b/lib/arel/math.rb @@ -0,0 +1,19 @@ +module Arel + module Math + def *(other) + Arel::Nodes::Multiplication.new(self, other) + end + + def +(other) + Arel::Nodes::Addition.new(self, other) + end + + def -(other) + Arel::Nodes::Subtraction.new(self, other) + end + + def /(other) + Arel::Nodes::Division.new(self, other) + end + end +end diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb index 442b313593..4b97e28668 100644 --- a/lib/arel/nodes.rb +++ b/lib/arel/nodes.rb @@ -18,6 +18,7 @@ require 'arel/nodes/join_source' require 'arel/nodes/ordering' require 'arel/nodes/delete_statement' require 'arel/nodes/table_alias' +require 'arel/nodes/math_operation' # nary require 'arel/nodes/and' diff --git a/lib/arel/nodes/math_operation.rb b/lib/arel/nodes/math_operation.rb new file mode 100644 index 0000000000..d9820f1ece --- /dev/null +++ b/lib/arel/nodes/math_operation.rb @@ -0,0 +1,15 @@ +module Arel + module Nodes + class MathOperation < Binary + include Arel::Expressions + include Arel::Predications + include Arel::Math + end + + class Multiplication < MathOperation; end + class Division < MathOperation; end + class Addition < MathOperation; end + class Subtraction < MathOperation; end + + end +end
\ No newline at end of file diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb index f76c1491ee..f30557e509 100644 --- a/lib/arel/visitors/to_sql.rb +++ b/lib/arel/visitors/to_sql.rb @@ -373,6 +373,22 @@ key on UpdateManager using UpdateManager#key= alias :visit_Time :quoted alias :visit_TrueClass :quoted + def visit_Arel_Nodes_Multiplication o + "#{visit o.left} * #{visit o.right}" + end + + def visit_Arel_Nodes_Division o + "#{visit o.left} / #{visit o.right}" + end + + def visit_Arel_Nodes_Addition o + "(#{visit o.left} + #{visit o.right})" + end + + def visit_Arel_Nodes_Subtraction o + "(#{visit o.left} - #{visit o.right})" + end + def visit_Array o o.empty? ? 'NULL' : o.map { |x| visit x }.join(', ') end diff --git a/test/support/fake_record.rb b/test/support/fake_record.rb index 54f73489c9..babf5fa25b 100644 --- a/test/support/fake_record.rb +++ b/test/support/fake_record.rb @@ -6,20 +6,26 @@ module FakeRecord attr_reader :tables, :columns_hash def initialize - @tables = %w{ users photos developers } + @tables = %w{ users photos developers products} @columns = { 'users' => [ Column.new('id', :integer), Column.new('name', :string), Column.new('bool', :boolean), - Column.new('created_at', :date), + Column.new('created_at', :date) + ], + 'products' => [ + Column.new('id', :integer), + Column.new('price', :decimal) ] } @columns_hash = { - 'users' => Hash[@columns['users'].map { |x| [x.name, x] }] + 'users' => Hash[@columns['users'].map { |x| [x.name, x] }], + 'products' => Hash[@columns['products'].map { |x| [x.name, x] }] } @primary_keys = { - 'users' => 'id' + 'users' => 'id', + 'products' => 'id' } end diff --git a/test/visitors/test_to_sql.rb b/test/visitors/test_to_sql.rb index 2d5549ca43..c47fd57a28 100644 --- a/test/visitors/test_to_sql.rb +++ b/test/visitors/test_to_sql.rb @@ -194,6 +194,28 @@ module Arel end end + describe "Nodes::MathOperation" do + it "should handle Multiplication" do + node = Arel::Attributes::Decimal.new(Table.new(:products), :price) * Arel::Attributes::Decimal.new(Table.new(:currency_rates), :rate) + @visitor.accept(node).must_equal %("products"."price" * "currency_rates"."rate") + end + + it "should handle Division" do + node = Arel::Attributes::Decimal.new(Table.new(:products), :price) / 5 + @visitor.accept(node).must_equal %("products"."price" / 5) + end + + it "should handle Addition" do + node = Arel::Attributes::Decimal.new(Table.new(:products), :price) + 6 + @visitor.accept(node).must_equal %(("products"."price" + 6)) + end + + it "should handle Subtraction" do + node = Arel::Attributes::Decimal.new(Table.new(:products), :price) - 7 + @visitor.accept(node).must_equal %(("products"."price" - 7)) + end + end + describe "Nodes::NotIn" do it "should know how to visit" do node = @attr.not_in [1, 2, 3] |