Git Product home page Git Product logo

squeel's Introduction

Archived

This project is archived.

Squeel Build Status endorse

Squeel lets you write your Active Record queries with fewer strings, and more Ruby, by making the Arel awesomeness that lies beneath Active Record more accessible.

Squeel lets you rewrite...

Article.where ['created_at >= ?', 2.weeks.ago]

...as...

Article.where{created_at >= 2.weeks.ago}

This is a good thing. If you don't agree, Squeel might not be for you. The above is just a simple example -- Squeel's capable of a whole lot more. Keep reading.

Getting started

In your Gemfile:

gem "squeel"  # Last officially released gem
# gem "squeel", :git => "git://github.com/activerecord-hackery/squeel.git" # Track git repo

Then bundle as usual.

If you'd like to customize Squeel's functionality by enabling core extensions for hashes or symbols, or aliasing some predicates, you can create a sample initializer with:

$ rails g squeel:initializer

The Squeel Query DSL

Squeel enhances the normal Active Record query methods by enabling them to accept blocks. Inside a block, the Squeel query DSL can be used. Note the use of curly braces in these examples instead of parentheses. {} denotes a Squeel DSL query.

Stubs and keypaths are the two primary building blocks used in a Squeel DSL query, so we'll start by taking a look at them. Most of the other examples that follow will be based on this "symbol-less" block syntax.

An important gotcha, before we begin: The Squeel DSL works its magic using instance_eval. If you've been working with Ruby for a while, you'll know immediately that this means that inside a Squeel DSL block, self isn't the same thing that it is outside the block.

This carries with it an important implication: Instance variables and instance methods inside the block won't refer to your object's variables/methods.

Don't worry, Squeel's got you covered. Use one of the following methods to get access to your object's methods and variables:

  1. Assign the variable locally before the DSL block, and access it as you would normally.
  2. Supply an arity to the DSL block, as in Person.where{|q| q.name == @my_name} Downside: You'll need to prefix stubs, keypaths, and functions (explained below) with the DSL object.
  3. Wrap the method or instance variable inside the block with my{}. Person.where{name == my{some_method_to_return_a_name}}

Stubs

Stubs are, for most intents and purposes, just like Symbols in a normal call to Relation#where (note the need for doubling up on the curly braces here, the first ones start the block, the second are the hash braces):

Person.where{{name => 'Ernie'}}
# => SELECT "people".* FROM "people"  WHERE "people"."name" = 'Ernie'

You normally wouldn't bother using the DSL in this case, as a simple hash would suffice. However, stubs serve as a building block for keypaths, and keypaths are very handy.

KeyPaths

A Squeel keypath is essentially a more concise and readable alternative to a deeply nested hash. For instance, in standard Active Record, you might join several associations like this to perform a query:

Person.joins(:articles => {:comments => :person})
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
#    INNER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"

With a keypath, this would look like:

Person.joins{articles.comments.person}

A keypath can exist in the context of a hash, and is normally interpreted relative to the current level of nesting. It can be forced into an "absolute" path by anchoring it with a ~, like:

~articles.comments.person

This isn't quite so useful in the typical hash context, but can be very useful when it comes to interpreting functions and the like. We'll cover those later.

Predicates

All of the Arel "predication" methods can be accessed inside the Squeel DSL, via their method name, an alias, or an an operator, to create Arel predicates, which are used in WHERE or HAVING clauses.

SQL Predication Operator Alias
= eq ==
!= not_eq != (1.9 only), ^ (1.8)
LIKE matches =~ like
NOT LIKE does_not_match !~ (1.9 only) not_like
< lt <
<= lteq <= lte
> gt >
>= gteq >= gte
IN in >>
NOT IN not_in <<

Let's say we want to generate this simple query:

SELECT "people".* FROM people WHERE "people"."name" = 'Joe Blow'

All of the following will generate the above SQL:

Person.where(:name => 'Joe Blow')
Person.where{{name => 'Joe Blow'}}
Person.where{{name.eq => 'Joe Blow'}}
Person.where{name.eq 'Joe Blow'}
Person.where{name == 'Joe Blow'}

Not a very exciting example since equality is handled just fine via the first example in standard Active Record. But consider the following query:

SELECT "people".* FROM people
WHERE ("people"."name" LIKE 'Ernie%' AND "people"."salary" < 50000)
  OR  ("people"."name" LIKE 'Joe%' AND "people"."salary" > 100000)

To do this with standard Active Record, we'd do something like:

Person.where(
  '(name LIKE ? AND salary < ?) OR (name LIKE ? AND salary > ?)',
  'Ernie%', 50000, 'Joe%', 100000
)

With Squeel:

Person.where{(name =~ 'Ernie%') & (salary < 50000) | (name =~ 'Joe%') & (salary > 100000)}

Here, we're using & and | to generate AND and OR, respectively.

There are two obvious but important differences between these two code samples, and both of them have to do with context.

  1. To read code with SQL interpolation, the structure of the SQL query must first be considered, then we must cross-reference the values to be substituted with their placeholders. This carries with it a small but perceptible (and annoying!) context shift during which we stop thinking about the comparison being performed, and instead play "count the arguments", or, in the case of named/hash interpolations, "find the word". The Squeel syntax places both sides of each comparison in proximity to one another, allowing us to focus on what our code is doing.

  2. In the first example, we're starting off with Ruby, switching context to SQL, and then back to Ruby, and while we spend time in SQL-land, we're stuck with SQL syntax, whether or not it's the best way to express what we're trying to do. With Squeel, we're writing Ruby from start to finish. And with Ruby syntax comes flexibility to express the query in the way we see fit.

Predicate aliases

That last bit is important. We can mix and match predicate methods with operators and take advantage of Ruby's operator precedence or parenthetical grouping to make our intentions more clear, on the first read-through. And if we don't like the way that the existing predications read, we can create our own aliases in a Squeel configure block:

Squeel.configure do |config|
  config.alias_predicate :is_less_than, :lt
end
Person.where{salary.is_less_than 50000}.to_sql
# => SELECT "people".* FROM "people"  WHERE "people"."salary" < 50000

And while we're on the topic of helping you make your code more expressive...

Compound conditions

Let's say you want to check if a Person has a name like one of several possibilities.

names = ['Ernie%', 'Joe%', 'Mary%']
Person.where('name LIKE ? OR name LIKE ? OR name LIKE ?', *names)

But you're smart, and you know that you might want to check more or less than 3 names, so you make your query flexible:

Person.where((['name LIKE ?'] * names.size).join(' OR '), *names)

Yeah... that's readable, all right. How about:

Person.where{name.like_any names}
# => SELECT "people".* FROM "people"
#    WHERE (("people"."name" LIKE 'Ernie%' OR "people"."name" LIKE 'Joe%' OR "people"."name" LIKE 'Mary%'))

I'm not sure about you, but I much prefer the latter. In short, you can add _any or _all to any predicate method, and it would do what you expect, when given an array of possibilities to compare against.

Sifters

Sifters are like little snippets of conditions that take parameters. Let's say that you have a model called Article, and you often want to query for articles that contain a string in the title or body. So you write a scope:

def self.title_or_body_contains(string)
  where{title.matches("%#{string}%") | body.matches("%#{string}%")}
end

But then you want to query for people who wrote an article that matches these conditions, but the scope only works against the model where it was defined. So instead, you write a sifter:

class Article < ActiveRecord::Base
  sifter :title_or_body_contains do |string|
    title.matches("%#{string}%") | body.matches("%#{string}%")
  end
end

Now you can write...

Article.where{sift :title_or_body_contains, 'awesome'}
# => SELECT "articles".* FROM "articles"
#    WHERE ((
#      "articles"."title" LIKE '%awesome%'
#      OR "articles"."body" LIKE '%awesome%'
#    ))

... or ...

Person.joins(:articles).
       where{
         {articles => sift(:title_or_body_contains, 'awesome')}
       }
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    WHERE ((
#      "articles"."title" LIKE '%awesome%'
#      OR "articles"."body" LIKE '%awesome%'
#    ))

Or, you can just modify your previous scope, changing where to squeel:

def self.title_or_body_contains(string)
  squeel{title.matches("%#{string}%") | body.matches("%#{string}%")}
end

Subqueries

You can supply an ActiveRecord::Relation as a value for a predicate in order to use a subquery. So, for example:

awesome_people = Person.where{awesome == true}
Article.where{author_id.in(awesome_people.select{id})}
# => SELECT "articles".* FROM "articles"
#    WHERE "articles"."author_id" IN (SELECT "people"."id" FROM "people"  WHERE "people"."awesome" = 't')

Joins

Squeel adds a couple of enhancements to joins. First, keypaths can be used as shorthand for nested association joins. Second, you can specify join types (inner and outer), and a class in the case of a polymorphic belongs_to relationship.

Person.joins{articles.outer}
# => SELECT "people".* FROM "people"
#    LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
Note.joins{notable(Person).outer}
# => SELECT "notes".* FROM "notes"
#    LEFT OUTER JOIN "people"
#      ON "people"."id" = "notes"."notable_id"
#      AND "notes"."notable_type" = 'Person'

These can also be used inside keypaths:

Note.joins{notable(Person).articles}
# => SELECT "notes".* FROM "notes"
#    INNER JOIN "people" ON "people"."id" = "notes"."notable_id"
#      AND "notes"."notable_type" = 'Person'
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"

You can refer to these associations when constructing other parts of your query, and they'll be automatically mapped to the proper table or table alias This is most noticeable when using self-referential associations:

Person.joins{children.parent.children}.
       where{
         (children.name.like 'Ernie%') |
         (children.parent.name.like 'Ernie%') |
         (children.parent.children.name.like 'Ernie%')
       }
# => SELECT "people".* FROM "people"
#    INNER JOIN "people" "children_people" ON "children_people"."parent_id" = "people"."id"
#    INNER JOIN "people" "parents_people" ON "parents_people"."id" = "children_people"."parent_id"
#    INNER JOIN "people" "children_people_2" ON "children_people_2"."parent_id" = "parents_people"."id"
#    WHERE ((("children_people"."name" LIKE 'Ernie%'
#          OR "parents_people"."name" LIKE 'Ernie%')
#          OR "children_people_2"."name" LIKE 'Ernie%'))

Keypaths were used here for clarity, but nested hashes would work just as well.

You can also use a subquery in a join.

Notice:

  1. Squeel can only accept an ActiveRecord::Relation class of subqueries in a join.
  2. Use the chain with caution. You should call as first to get a Nodes::As, then call on to get a join node.
subquery = OrderItem.group(:orderable_id).select { [orderable_id, sum(quantity * unit_price).as(amount)] }
Seat.joins { [payment.outer, subquery.as('seat_order_items').on { id == seat_order_items.orderable_id}.outer] }.
              select { [seat_order_items.amount, "seats.*"] }
# => SELECT "seat_order_items"."amount", seats.*
#    FROM "seats"
#    LEFT OUTER JOIN "payments" ON "payments"."id" = "seats"."payment_id"
#    LEFT OUTER JOIN (
#      SELECT "order_items"."orderable_id",
#             sum("order_items"."quantity" * "order_items"."unit_price") AS amount
#      FROM "order_items"
#      GROUP BY "order_items"."orderable_id"
#    ) seat_order_items ON "seats"."id" = "seat_order_items"."orderable_id"

Includes

Includes works similarly with joins, it uses outer join defaultly. In Rails 4, you need to use references with includes together.

Rails 4+

Person.includes(:articles => {:comments => :person}).references(:all)
# => SELECT "people".* FROM "people"
#    LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
#    LEFT OUTER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"

With a keypath, this would look like:

Person.includes{articles.comments.person}.references(:all)

Rails 3.x

Person.includes(:articles => {:comments => :person})
# => SELECT "people".* FROM "people"
#    LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
#    LEFT OUTER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"

With a keypath, this would look like:

Person.includes{articles.comments.person}

Functions

You can call SQL functions just like you would call a method in Ruby...

Person.select{coalesce(name, '<no name given>')}
# => SELECT coalesce("people"."name", '<no name given>') FROM "people"

...and you can easily give it an alias:

person = Person.select{
  coalesce(name, '<no name given>').as(name_with_default)
}.first
person.name_with_default # name or <no name given>, depending on data

When you use a stub, symbol, or keypath inside a function call, it'll be interpreted relative to its place inside any nested associations:

Person.joins{articles}.group{articles.title}.having{{articles => {max(id) => id}}}
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    GROUP BY "articles"."title"
#    HAVING max("articles"."id") = "articles"."id"

If you want to use an attribute from a different branch of the hierarchy, use an absolute keypath (~) as done here:

Person.joins{articles}.group{articles.title}.having{{articles => {max(~id) => id}}}
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    GROUP BY "articles"."title"
#    HAVING max("people"."id") = "articles"."id"

SQL Operators

You can use the standard mathematical operators (+, -, *, /) inside the Squeel DSL to specify operators in the resulting SQL, or the op method to specify another custom operator, such as the standard SQL concatenation operator, ||:

p = Person.select{name.op('||', '-diddly').as(flanderized_name)}.first
p.flanderized_name
# => "Aric Smith-diddly"

As you can see, just like functions, these operations can be given aliases.

To select more than one attribute (or calculated attribute) simply put them into an array:

p = Person.select{[ name.op('||', '-diddly').as(flanderized_name),
                    coalesce(name, '<no name given>').as(name_with_default) ]}.first
p.flanderized_name
# => "Aric Smith-diddly"
p.name_with_default
# => "Aric Smith"

Compatibility with Active Record

Most of the new functionality provided by Squeel is accessed with the new block-style where{} syntax.

All your existing code that uses plain Active Record where() queries should continue to work the same after adding Squeel to your project with one exception: symbols as the value side of a condition (in normal where() clauses).

Symbols as the value side of a condition (in normal where() clauses)

If you have any where() clauses that use a symbol as the value side (right-hand side) of a condition, you will need to change the symbol into a string in order for it to continue to be treated as a value.

Squeel changes the meaning of symbols in the value of a condition to refer to the name of a column instead of simply treating the symbol as a string literal.

For example, this query:

Person.where(:first_name => :last_name)

produces this SQL query in plain Active Record:

SELECT people.* FROM people WHERE people.first_name = 'last_name'.

but produces this SQL query if you are using Squeel:

SELECT people.* FROM people WHERE people.first_name = people.last_name

Note that this new behavior applies to the plain where()-style expressions in addition to the new where{} Squeel style.

In order for your existing where() clauses with symbols to continue to behave the same, you must change the symbols into strings. These scopes, for example:

scope :active, where(:state => :active)
scope :in_state, lambda {|state| where(:state => state) }

should be changed to this:

scope :active, where(:state => 'active')
scope :in_state, lambda {|state| where(:state => state.to_s) }

For further information, see this post to the Rails list, this commit to the Active Record guides, #67, #75, and #171.

Compatibility with MetaWhere

While the Squeel DSL is the preferred way to access advanced query functionality, you can still enable methods on symbols to access Arel predications in a similar manner to MetaWhere:

Squeel.configure do |config|
  config.load_core_extensions :symbol
end
Person.joins(:articles => :comments).
       where(:articles => {:comments => {:body.matches => 'Hello!'}})
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
#    WHERE "comments"."body" LIKE 'Hello!'

This should help to smooth over the transition to the new DSL.

Contributions

If you'd like to support the continued development of Squeel, please consider making a donation.

To support the project in other ways:

  • Use Squeel in your apps, and let me know if you encounter anything that's broken or missing. A failing spec is awesome. A pull request is even better!
  • Spread the word on Twitter, Facebook, and elsewhere if Squeel's been useful to you. The more people who are using the project, the quicker we can find and fix bugs!

Copyright

Copyright ยฉ 2011 Ernie Miller

squeel's People

Contributors

bigxiang avatar danielrhodes avatar ernie avatar estum avatar fxn avatar iamvery avatar janko avatar joseluistorres avatar mariovisic avatar meskallito avatar peternixey avatar radar avatar richguk avatar scarroll32 avatar simi avatar snkashis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

squeel's Issues

(My)SQL BETWEEN syntax

Is it possible to use the BETWEEN syntax? I've tried various things but it seems to be unsupported:

Post.where{created_at = 1.week.ago..Time.zone.now} # => TypeError: Cannot visit Range
Post.where{created_at >> 1.week.ago..Time.zone.now} # => ArgumentError: bad value for range

If not supported yet, are there any plans to do so?

Thanks

find_by_columnname(:symbol) error or incorrect usage?

This likely could be me trying to use things incorrectly--I was just a bit surprised by it:

ree-1.8.7-2011.03 :001 > Partner.find_by_shortname('foo')
Partner Load (0.8ms) SELECT "partners".* FROM "partners" WHERE "partners"."shortname" = 'foo' LIMIT 1

ree-1.8.7-2011.03 :002 > Partner.find_by_shortname(:foo)
Partner Load (0.7ms) SELECT "partners".* FROM "partners" WHERE "partners"."shortname" = "partners"."foo" LIMIT 1
ActiveRecord::StatementInvalid: PGError: ERROR: column partners.foo does not exist
LINE 1: ...* FROM "partners" WHERE "partners"."shortname" = "partners"...

Is that expected?

This was with

config.load_core_extensions :hash

but not

config.load_core_extensions :symbol

no support for 'exists' subqueries

You can do:

Article.where{author_id.in( awesome_people.select{id} )} 

But the support for functions doesn't allow this:

Article.where{(author_id == 1) & ( exists(awesome_people.select{id.gt 1}) ) }

Error Stack:

TypeError: Cannot visit ActiveRecord::Relation
from /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:21:in visit' from /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:5:inaccept'
from /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/to_sql.rb:18:in accept' from /var/lib/gems/1.8/gems/activerecord-3.0.9.rc5/lib/active_record/connection_adapters/abstract/connection_pool.rb:111:inwith_connection'
from /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/to_sql.rb:16:in accept' from /var/lib/gems/1.8/gems/squeel-0.8.0/lib/squeel/visitors/base.rb:85:inquote'
from /var/lib/gems/1.8/gems/squeel-0.8.0/lib/squeel/visitors/predicate_visitor.rb:123:in visit_Squeel_Nodes_Function' from /var/lib/gems/1.8/gems/squeel-0.8.0/lib/squeel/visitors/predicate_visitor.rb:114:inmap'
from /var/lib/gems/1.8/gems/squeel-0.8.0/lib/squeel/visitors/predicate_visitor.rb:114:in visit_Squeel_Nodes_Function' from /var/lib/gems/1.8/gems/squeel-0.8.0/lib/squeel/visitors/base.rb:98:insend'

etc.

Support references across clauses

It would be great to be able to symbolically reference across clauses, just like in SQL. For example, if I'm transforming a column value with a function in the SELECT clause and aliasing it with a name, it would be nice to be able to refer to that name in the WHERE clause, without repeating the transformation. Also, if the transformation is complex or unfamiliar, the name helps to communicate the transformation's intent into the future.

I don't expect this is a performance issue (hopefully the RDBMS will not recalculate the value for ever appearance of the transformation), but it would aid maintainability to be able to use symbolic references.

Gemspec seems to have an invalid date (0.8.0)

Getting this error while trying to use the gem in my project:
Invalid gemspec in [/Users/tanel/.rvm/gems/ruby-1.9.2-p180/specifications/squeel-0.8.0.gemspec]: invalid date format in specification: "2011-06-13 00:00:00.000000000Z"

Note that this causes bundler to cry about the gem not being installed although it really is.

Ordering bug

Found this annoying issue today:

User.order(:created_at).first
=> SELECT `users`.* FROM `users` ORDER BY `users`.`created_at` LIMIT 1

User.order(:created_at).last
=> SELECT `users`.* FROM `users` ORDER BY `users`.`created_at` LIMIT 1

User.order("created_at").last
=> SELECT `users`.* FROM `users` ORDER BY created_at DESC LIMIT 1

When using squeel symbols, .last doesn't change the direction of the ORDER BY clause and issues a result that is the same as calling .first

Conflicting syntax with validations

Squeel does use the correct value due to conflicting syntax when used with custom validations.

validate :custom_validation

Ruby code

private 
  def custom_validation
    if Flag.where("flags.id != ?", id).exists?
      errors.add :base, 'Error"
    end
  end
end
#SQL: SELECT 1 FROM `flags` WHERE `flags`.`id` != 1 LIMIT 1

Squeel Code

private 
  def custom_validation
     if Flag.where{id != id}).exists?
       errors.add :base, 'Error"
    end
  end
end
#SQL: SELECT 1 FROM `flags` WHERE `flags`.`id` != `flags`.`id` LIMIT 1

Note: This is a shortened version of my actual code, I have additional where clauses in the validation

Cannot visit String

I'm getting this error just after installing squeel gem. I'm on Rails 3.1.rc5.

Cannot visit String


Extracted source (around line #22): 
19:         %th End time
20:         %th Spent
21: 
22:       = render :partial => 'task', :collection => @tasks

Can't call the lower SQL function

Hi,

first, thanks for this awesome gem!

We are moving from MetaWhere to Squeel but we run into the issue where we can't call the lower SQL function (other SQL functions can't be called too, but we are mainly interested in lower right now), like this:

User.where { lower(email) == "[email protected]" }

We got a undefined method 'with_connection' for ActiveRecord::Base:Class exception when we try to call this.

We are using Ruby 1.9.2, Rails 3.1 edge on the '3-1-stable' branch and Squeel edge.

Any idea why it's failing?

Thanks!

NoMethodError: undefined method `outer' for #<Class:0xa5a698c>

Hi Ernie, I have the following issue:

Given that:

class Measurement < ActiveRecord::Base
  belongs_to :project
end

In console:

  1. working with symbols everything is ok:
Measurement.joins {:project.outer}
=> Measurement Load (0.8ms)  SELECT "measurements".* FROM "measurements" LEFT OUTER JOIN "projects" ON "projects"."id" = "measurements"."project_id"
  1. working with the new syntax throws the following error:
Measurement.joins {project.outer}
=> NoMethodError: undefined method `outer' for #<Class:0xa5a698c>
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/activerecord-3.0.7/lib/active_record/base.rb:1009:in `method_missing'
    from (irb):4:in `block in irb_binding'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/squeel-0.6.0/lib/squeel/dsl.rb:31:in `instance_eval'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/squeel-0.6.0/lib/squeel/dsl.rb:31:in `eval'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/squeel-0.6.0/lib/squeel/adapters/active_record/3.0/relation.rb:199:in `joins'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/activerecord-3.0.7/lib/active_record/base.rb:442:in `joins'
    from (irb):4
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.7/lib/rails/commands/console.rb:44:in `start'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.7/lib/rails/commands/console.rb:8:in `start'
    from /home/diego/.rvm/gems/ruby-1.9.2-p180/gems/railties-3.0.7/lib/rails/commands.rb:23:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

ActiveRecord::StatementInvalid

user = User.where{ {login => params[:login]} | {email => params[:login]} }.first

produces
PGError: ERROR: syntax error at or near "[" LINE 1: SELECT "users".* FROM "users" WHERE (([]("params"."login")...

prepending table name when is already set causing invalid sql

Hi Ernie I have the following issue:

Code:

    Volume.select('DISTINCT volumes.*').
      joins(:measurements, :typology).
      includes(:volume_type, :typology).
      where('typologies.project_id' => typology.project_id).
      where('volumes.volume_type_id' => volume_type_id)

Generated sql:

SELECT DISTINCT volumes.* FROM "volumes" INNER JOIN 
"measurements" ON "measurements"."volume_id" = "volumes"."id" INNER JOIN
 "typologies" ON "typologies"."id" = "volumes"."typology_id" WHERE
 "volumes"."typologies.project_id" = 799 AND "volumes"."volumes.volume_type_id" = 225

As you can see Squeel is prepending the "volumes" table name when this value is already set to a different table in the join, without Squeel this code works perfect.

Name Error

It's not called EQL.

Sorry, couldn't resist.

.order causes failure when used with kaminari's .page

Using kaminari for pagination, I have a Squeel line to render a message inbox. This was working fine for weeks, until my project decided to switch from SQLite to the Postgres adapter, 'pg version 0.11.0'.

  1. For testing, I went back and checked it again on a SQLite DB, and it's fine.

  2. Without the ".page" call, this statement is fine.

  3. Without the .order clause, this statement is fine.

  4. A really simple combination, User.order{created_at}.page 1, works fine.

    class MessageThread
    has_many :messages, :order => 'created_at', :dependent => :destroy
    belongs_to :user

    def self.in_inbox
    where{message_thread_users.hidden_at == nil}.
    where{ exists( Message.where{ (user_id ^ message_thread_users.user_id) & (message_thread_id == message_threads.id)}.select{1}) }
    end

    def self.by_recency
    includes{:messages}.order{[message_threads.updated_at.desc, messages.created_at.desc, message_threads.id.desc]}
    end

    end

    class User
    has_many :message_thread_users
    has_many :message_threads, :through => :message_thread_users
    end

    irb(main):013:0> current_user.message_threads.in_inbox.includes{:messages}.order{message_threads.updated_at.desc}.page 1

    Hirb Error: undefined method index' for #<Arel::Nodes::Ordering:0xb580914c> /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/postgresql.rb:51:inaliased_orders'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:17:in each_with_index' /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/postgresql.rb:47:ineach'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/postgresql.rb:47:in each_with_index' /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/postgresql.rb:47:inaliased_orders'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/postgresql.rb:19:in visit_Arel_Nodes_SelectStatement' /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:15:insend'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:15:in visit' /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/visitor.rb:5:inaccept'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/to_sql.rb:18:in accept' /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/connection_adapters/abstract/connection_pool.rb:111:inwith_connection'
    /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/visitors/to_sql.rb:16:in accept' /var/lib/gems/1.8/gems/arel-2.0.10/lib/arel/tree_manager.rb:20:into_sql'
    /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation.rb:64:in to_a' /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation.rb:15:incollect'
    /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation/finder_methods.rb:236:in construct_limited_ids_condition' /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation/finder_methods.rb:223:inapply_join_dependency'
    /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation/finder_methods.rb:212:in construct_relation_for_association_find' /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation/finder_methods.rb:192:infind_with_associations'
    /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation.rb:64:in to_a' /var/lib/gems/1.8/gems/activerecord-3.0.9/lib/active_record/relation.rb:366:inmethod_missing'
    /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/formatter.rb:88:in Array' /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/formatter.rb:88:indetermine_output_class'
    /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/formatter.rb:53:in format_output' /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/view.rb:204:inrender_output'
    /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/view.rb:123:in view_output' /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/view.rb:200:inview_or_page_output'
    /var/lib/gems/1.8/gems/hirb-0.4.5/lib/hirb/view.rb:186:in output_value' /usr/lib/ruby/1.8/irb.rb:159:ineval_input'
    /usr/lib/ruby/1.8/irb.rb:271:in signal_status' /usr/lib/ruby/1.8/irb.rb:155:ineval_input'
    /usr/lib/ruby/1.8/irb/ruby-lex.rb:244:in each_top_level_statement' /usr/lib/ruby/1.8/irb/ruby-lex.rb:230:inloop'
    /usr/lib/ruby/1.8/irb/ruby-lex.rb:230:in each_top_level_statement' /usr/lib/ruby/1.8/irb/ruby-lex.rb:229:incatch'
    /usr/lib/ruby/1.8/irb/ruby-lex.rb:229:in each_top_level_statement' /usr/lib/ruby/1.8/irb.rb:154:ineval_input'
    /usr/lib/ruby/1.8/irb.rb:71:in start' /usr/lib/ruby/1.8/irb.rb:70:incatch'
    /usr/lib/ruby/1.8/irb.rb:70:in start' /var/lib/gems/1.8/gems/railties-3.0.9/lib/rails/commands/console.rb:44:instart'
    /var/lib/gems/1.8/gems/railties-3.0.9/lib/rails/commands/console.rb:8:in start' /var/lib/gems/1.8/gems/railties-3.0.9/lib/rails/commands.rb:23 script/rails:6:inrequire'
    script/rails:6

Rails 3.0.9, ruby 1.8.7, Squeel 0.8.5, kaminari 0.12.4

private method `desc' called for :created_at:Symbol

Rails 3.0.7

<class:InfoBox>': private methoddesc' called for :created_at:Symbol (NoMethodError)

When switching back to meta_where works ok.

Here's scope:

default_scope where("status != 'Deleted'").order(:created_at.desc)

Thx 4 reply :)

More issues with has_many :through

I found today that squeel breaks creation of has_many through records

In model:

class Survey < ActiveRecord::Base
  has_many :survey_questions, :dependent => :destroy
  has_many :questions, :through => :survey_questions
end

In console:

Survey.first.questions.create

Results in:

ActiveRecord::UnknownAttributeError: unknown attribute: survey_id

KeyPaths not responding to !=

Hiya, found an issue trying to use != on a fully qualified attribute; here's a minimal example.

Person.where{first_name != 'Andy'}.to_sql
#=> SELECT "people".* FROM "people" WHERE "people"."first_name" != 'Andy'

Person.where{person.first_name != 'Andy'}.to_sql
#=> SELECT "people".* FROM "people" 

person.first_name == 'Andy' produces the WHERE clause correctly.

Same issue whether on edge Rails or Rails 3.0.7 (both under Ruby 1.9.2)

1.9.2 issues with multiple joins to the same table

I'm getting some strange behavior running ruby 1.9.2 with the latest squeel 0.8.6 and rails 3.0.9.

I have a scope which works fine most of the time but other times gives a TypeError: Cannot visit FalseClass error with the same input arguments.
I've included the relavent code below. I would investigate further but my squeel + AR knowledge is a bit limited.

# app/models/blocking.rb
class Blocking < ActiveRecord::Base
  belongs_to :blocker, :class_name => 'User'
  belongs_to :blockee, :class_name => 'User'
end

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :outgoing_blocks, :class_name => 'Blocking', :foreign_key => :blocker_id
  has_many :incoming_blocks, :class_name => 'Blocking', :foreign_key => :blockee_id

  def self.unblocked(our_user)
    unblocked = joins{[outgoing_blocks.outer, incoming_blocks.outer]}
    unblocked = unblocked.where{ ((outgoing_blocks.blocker_id.not_eq our_user.id) & (incoming_blocks.blocker_id.not_eq our_user.id)) }
    unblocked = unblocked.where{ ((outgoing_blocks.blockee_id.not_eq our_user.id) & (incoming_blocks.blockee_id.not_eq our_user.id)) }
  end
end

# db/schema.rb
ActiveRecord::Schema.define(:version => 20110804072533) do
  create_table "users", :force => true do |t|
  end
  create_table "blockings", :force => true do |t|
    t.integer  "blocker_id", :null => false
    t.integer  "blockee_id", :null => false
  end
end

Here is the stack trace:

ruby-1.9.2-p290 :050 > User.unblocked(User.first)
TypeError: Cannot visit FalseClass
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:104:in `rescue in visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:97:in `visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:64:in `visit_Squeel_Nodes_KeyPath'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:97:in `visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `block in visit_Array'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `map'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `visit_Array'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:97:in `visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:197:in `visit_Squeel_Nodes_And'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:97:in `visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `block in visit_Array'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `map'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/predicate_visitor.rb:43:in `visit_Array'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:97:in `visit'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/visitors/base.rb:26:in `accept'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/squeel-0.8.6/lib/squeel/adapters/active_record/3.0/relation.rb:82:in `build_arel'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/activerecord-3.0.9/lib/active_record/relation/query_methods.rb:149:in `arel'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/activerecord-3.0.9/lib/active_record/relation.rb:64:in `to_a'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/activerecord-3.0.9/lib/active_record/relation.rb:359:in `inspect'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/railties-3.0.9/lib/rails/commands/console.rb:44:in `start'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/railties-3.0.9/lib/rails/commands/console.rb:8:in `start'
  from /Users/mariovisic/.rvm/gems/[email protected]/gems/railties-3.0.9/lib/rails/commands.rb:23:in `<top (required)>'
  from script/rails:6:in `require'
  from script/rails:6:in `<main>'ruby-1.9.2-p290 :051 > User.unblocked(User.first)

If you could point me in the right direction that would help, I might be able to patch it myself. Let me know if you need any further info.

Cheers

TypeError: Cannot visit Arel::SelectManager

Hi,

I'm trying to do the following:

class Shoutout < ActiveRecord::Base
  scope :during_room_availability, lambda { |room|
    where{
      datediff(check_out, check_in).eq  
      Availability.select{count(`*`)}.where{
        (room_id.eq room.id) & (inquiry_id.eq nil) & 
        (date.gte shoutouts.check_in) & (date.lte date_add(shoutouts.check_out, `INTERVAL -1 DAY`))} }
}
end

and am expecting the following SQL output:

WHERE datediff(shoutouts.check_out, shoutouts.check_in) = 
(SELECT COUNT(*) FROM availabilities WHERE 
  availabilities.room_id = #{room.id} AND availabilities.inquiry_id IS NULL AND 
  availabilities.date >= `shoutouts`.check_in AND availabilities.date <= date_add(shoutouts.check_out, INTERVAL -1 DAY))

But unfortunately this fails with TypeError: Cannot visit Arel::SelectManager

Any other way I can achieve this?

Cannot visit error when using normal ARel interface

If I do:

Post.where(:network_id => networks, :author_id => current_user).order('scheduled_at DESC, created_at DESC')

while using squeel, I get a cannot visit error on the User class.
If I change current_user to current_user.id, the error changes to 'cannot visit Arel::SelectManager'
Which in turn goes away if I change networks to networks.collect {|n| n.id}.

Which, put together makes:

Post.where(:network_id => networks.collect {|n| n.id}, :author_id => current_user.id).order('scheduled_at DESC, created_at DESC')

So it appears that squeel presently breaks the normal ARel where function's ability to implicity call id on a model or collection of models.

This occured while using Rails 3.0.7, Ruby 1.8.7-p334, Ubuntu 11.04 and squeel 0.5.5

How to search with 'matches %...%' ?

Hello,

When I do

Person.search(:name_matches => 'Boo').result

the SQL request looks like

SELECT "people".* FROM "people" WHERE "people"."name" LIKE 'Boo'

The :name_matches => 'Boo' part is what I get in params[:q]. What's the easiest way to generate the same SQL request with value surrounded by % ?

Thankyou

Parent.includes(:children) breaks with Squeel (when no primary key)

For a legacy "Parent" model with no primary key, the .includes() method works correctly without Squeel:

`````` Parent.includes(:children)```
gives

SELECT children.* FROM children WHERE (children.id IN (x1, x2, x3, ...))```

With Squeel, the same command gives
```SELECT parents.* FROM parents
SELECT children.* FROM children WHERE (children.id = x1)```
where x1 is the same id in both cases.

This is also the case when using Squeel notation,
```Parent.includes{children}```

[Feature request] Time calculation

For both MetaWhere and Squeel it would be really nice to have database independent date computation.
Normally databases have methods such as HOUR, MINUTES, DAY, etc. For application doing a lot of querying on date, having the possibility to write where(:date.hour.gte => 8).where(:date.hour.lte => 12) would be a great thing !
I don't know how difficult this can be ... so just creating an issue to propose the idea.

Not in with empty array

Hi,
As you can see from this debug session:
Account.where{id.not_in []}.to_sql => "SELECT accounts.* FROM accounts WHERE accounts.id NOT IN (NULL)"
Account.where{id.not_in [nil]}.to_sql => "SELECT accounts.* FROM accounts WHERE accounts.id NOT IN (NULL)"
Account.where{id.not_in [nil,nil,nil]}.to_sql => "SELECT accounts.* FROM accounts WHERE accounts.id NOT IN (NULL, NULL, NULL)"

the behaviour is the same when the array is empty and when the array includes only a nil element.

The behaviour should be (IMHO):
Account.where{id.not_in []}.to_sql => "SELECT accounts.* FROM accounts"
Account.where{id.not_in [nil]}.to_sql => "SELECT accounts.* FROM accounts WHERE accounts.id NOT IN (NULL)"

has many through and 'Not unique table/alias'

When joining on the table used in the through portion of a has_many :through=>... the sequel joins the same table twice
Simplifying my query down and using the schema from the specs it is similar to (but with more conditions) Person.first.authored_article_comments.joins{article.tags} which produces the following invalid sequel and throws a 'Not unique table/alias' error due to joining on "articles" twice

SELECT "comments".* FROM "comments" INNER JOIN "articles" ON "articles"."id" = "comments"."article_id" INNER JOIN "articles_tags" ON "articles_tags"."article_id" = "articles"."id" INNER JOIN "tags" ON "tags"."id" = "articles_tags"."tag_id" INNER JOIN "articles" ON "comments"."article_id" = "articles"."id" WHERE "articles"."person_id" = 1

Is there a solution/workaround for this?

breaks has_one :through in Rails 3.1

Squeel throws an "ArgumentException: Unable to get table for [join_table]" when attempting to query through an aliased has_one :through association

class Team < ActiveRecord::Base
  has_many :team_memberships
  has_many :members, through: :team_memberships

  has_one :captaincy, class_name: 'TeamMembership', conditions: {captaincy: true}
  has_one :captain, through: :captaincy, source: :member
end

(the join table TeamMembership has a boolean column captaincy)

When using squeel, Team.captain throws "Unable to get table for team_memberships" on Rails 3.1 (but works on 3.0, and 3.1 without squeel); Team.captaincy works in all cases.

Stack trace: http://pastie.org/1913012

Aliased column for aggregated results and (missing) subselects

Domain.group(:publication_id).select { count('*').as(cnt) }.where { cnt > 1 }

should generate a subquery instead of

SELECT count('*') AS cnt FROM "domains" WHERE "domains"."cnt" > 1 GROUP BY "domains"."publication_id"

which obviously fails due to the scoping of cnt.

invalid date format in gemspec

When installing the squeel gem on our build server (debian, rvm, ruby 1.9.2), I get the following error:

Invalid gemspec in [/home/bamboo/.rvm/gems/ruby-1.9.2-p136@rem2/specifications/squeel-0.8.4.gemspec]: invalid date format in specification: "2011-06-20 00:00:00.000000000Z"

No problems on my local OSX. After editing the gemspec file manually on the server, everything runs fine.

Use object's attributes inside squeel block

I've stumbled upon an issue (rather a missing feature I guess) where I wasn't able to do something like this:

class Post < ActiveRecord::Base
  belongs_to :parent, :class_name => 'Post' # so it has a parent_id attribute

  def posts_without_current_parent
    Post.where{id != parent_id} # => SELECT `posts`.* FROM `posts` WHERE `posts`.`id` != `posts`.`parent_id`
  end
end

As you can see it sees parent_id as an attribute for the new query and not an attribute coming from the main Post object. Doing something like self.parent_id did not resolve it either.
A workaround for this can be:

def posts_without_current_parent
  the_id = parent_id # => 1
  Post.where{id != the_id} # => SELECT `posts`.* FROM `posts` WHERE `posts`.`id` != 1
end

This works as expected but is less clean.

Am I overlooking something or is this intended not to work? Anyway it would be cool to be able to use attributes inside the Squeel block.

cannot access member variables inside a squeel block

This might be by design, but anyway: Inside a squeel block I obviously have a different context. Therefore, I cannot access member variables from the class containing the squeel query (the following code does not make much sense, just wants to show the the problem):

class Repository

def initialize(name)
@name = name
end

def get_people
# @name is nil inside the squeel block
Person.where { name == @name }
end

end

aliased columns do not work in where conditions

class Award < ActiveRecord::Base
    # has a column  :type, :string
end

Award.select{type.as foo}.where{foo == 'SimpleAward'}

generates the error

  Award Load (0.1ms)  SELECT "awards"."type" AS foo FROM "awards" WHERE "awards"."foo" = 'SimpleAward'
SQLite3::SQLException: no such column: awards.foo: SELECT "awards"."type" AS foo FROM "awards"  WHERE "awards"."foo" = 'SimpleAward'
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: awards.foo: SELECT "awards"."type" AS foo FROM "awards"  WHERE "awards"."foo" = 'SimpleAward'

however I'd expect it to generate the SQL

  SELECT "awards"."type" AS foo FROM "awards" WHERE "foo" = 'SimpleAward'

dropping the prefix.

Is this possible or does it contradict with another use case of SQUEEL. My use case is a bit more complex than the above. I'm actually creating an extension to ActiveRecord with objects not backed by a table.

class VirtualRecord < ActiveRecord::Base
  def self.columns() 
    @columns ||= [] 
  end  

  def self.columns_hash()
    @columns_hash ||= {}
  end

  def self.column(name, sql_type = :string, default = nil, null = true)  
    column = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)  

    columns << column
    columns_hash[name.to_s] = column   
  end  

  self.abstract_class = true

  def self.table_name
    to_s.underscore.pluralize
  end

  def readonly?
    true
  end

end

and

class AwardTypeView < VirtualRecord
  column :award_type, :string

  default_scope do
    select{awards.type.as award_type}.from("awards")
  end
end

where

AwardTypeView.all

returns data as expected but

AwardTypeView.where{award_type="SimpleAward"}

generates the same error as the first example

AwardTypeView.where{award_type=="SimpleAward"}

SELECT "awards"."type" AS award_type FROM awards WHERE  "award_type_views"."award_type" = 'SimpleAward'

again the aliased virtual column award_type is prefixed by the class name of the object **award_type_views".

The problem is not specific to my hacking active record but I thought you might be interested in my use case.

Regards

Brad

unable to generate subquery

Hi,

I'm trying to generate :

SELECT "contrat".*
FROM "contrat"
WHERE nb_doms("contrat"."contrat") != (select count(id) from domains where domains.contrat_id = contrat.id_contrat);

but I can't get my way around having it generate the right subquery, most of the time, it generate something that does not make sense like if I do :

nb_doms(contrat.contrat) != domains.select{count(id)}

it does :

nb_doms("contrat"."contrat") != "domains"."select"

and the rest of the time, it gives me a "Cannot visit Arel::SelectManager", like if I do :

nb_doms(contrat.contrat) != Domain.where{contrat_id == contrat.id}.select{count(id)}

OCIError: ORA-01704: string literal too long

Hi,

I'm receiving the following error when using the activerecord-oracle_enhanced-adapter and squeel:

OCIError: ORA-01704: string literal too long

I'm using ActiveRecord/Rails 3.0.9, activerecord-oracle_enhanced-adapter 1.3.2 and squeel 0.8.5.

To reproduce this error:

$ cat Gemfile
source 'http://rubygems.org'
gem 'rails', '3.0.9'
gem 'ruby-oci8'
gem 'activerecord-oracle_enhanced-adapter'
gem "squeel"

$ rails generate model Post content:text

$ rake db:migrate

$ rails console
$> p = Post.create(:content => "a")
$> p.content = "b" * 4001
$> p.save

The problem is that the value is longer than 4000 characters even though it is a clob that allows for more, and the value is placed directly in the SQL query as a string which is not allowed. The weird thing is that the create call with a value for content that is longer than 4000 characters works perfectly.

If I don't put squeel in the Gemfile, then the save above works perfectly.

Support order on column of joined table

How can I order on a column of a joined table? Is this supported? I couldn't find it documented nor by trial and error.. :(

Comment.joins(:user).order(:user => {:firstname.desc})

SELECT * FROM comments JOIN users ON (comments.user_id=users.id) ORDER BY users.firstname DESC

set_table_name

It is not work with set_table_name, only with real db table name. It is needed to work with legacy databases.

[0.7.4] .as() doesn't appear to alias selected columns

When attempting to alias a selected field as a different name I'm trying this:

>> Account.select{test.as(my_field_name)}.debug_sql
=> "SELECT as(\"test\".\"my_field_name\") FROM \"accounts\""

However, if I use the .op syntax to do a whole lot of nothing, it seems to work:

>> Account.select{test.op('||', '').as(my_field_name)}.debug_sql
=> "SELECT \"accounts\".\"test\" || '' AS my_field_name FROM \"accounts\""

Is there a way using the squeel DSL to generate this SQL:

SELECT "accounts"."test" AS my_field_name FROM "accounts"

Note that the reason I want this is because I'm joining a table to itself and pulling the same attribute from the two different tables, and would like the named specifically.

Also the .as() doesn't appear to alias joined association table names. For instance:

>> Account.joins{negative_overflow_account.as(test_relation)}.debug_sql
ActiveRecord::ConfigurationError: #<Squeel::Nodes::Function:0x00000100ea3750 @name=:as, @args=[test_relation]>

Is this behavior expected in a future version? Thanks!

Support subqueries in select

May be it's already suppored and I'm only to dump to get it working.... otherwise it'd be nice if it'd be supported :)

latest_post = Comment.select([:id]).where{user_id == user.id}.order{id.desc}.limit(1)
User.select([:id, username, latest_post => :latest_post_id])

Should produce something like

SELECT id,username,(SELECT id FROM comments WHERE user_id=users.id ORDER BY id DESC LIMIT 1) AS latest_post_id FROM users

cannot handle custom range classes

I have a custom date range class, derived from Range that lives in lib/extensions:

class DateRange < Range
...
end

class Person < ActiveRecord::Base
scope :born_within, lambda { |date_range| where {{ date_of_birth => date_range }}}
end

In the rails console:

range = DateRange.new Date.new(1960,1,1), Date.new(170,12,31)
people = Person.born_within range

TypeError: Cannot visit DateRange
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:25:in rescue in visit' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:19:invisit'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:347:in visit_Arel_Nodes_Equality' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:19:invisit'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:329:in block in visit_Arel_Nodes_And' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:329:inmap'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:329:in visit_Arel_Nodes_And' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:19:invisit'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:138:in block in visit_Arel_Nodes_SelectCore' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:138:inmap'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:138:in visit_Arel_Nodes_SelectCore' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:123:inblock in visit_Arel_Nodes_SelectStatement'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:123:in map' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:123:invisit_Arel_Nodes_SelectStatement'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/sqlite.rb:7:in visit_Arel_Nodes_SelectStatement' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:19:invisit'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/visitor.rb:5:in accept' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:20:inblock in accept'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/activerecord-3.1.0.rc4/lib/active_record/connection_adapters/abstract/connection_pool.rb:174:in with_connection' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/visitors/to_sql.rb:18:inaccept'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/arel-2.1.3/lib/arel/tree_manager.rb:21:in to_sql' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/activerecord-3.1.0.rc4/lib/active_record/relation.rb:109:into_a'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/activerecord-3.1.0.rc4/lib/active_record/relation.rb:423:in inspect' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:45:instart'
from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/railties-3.1.0.rc4/lib/rails/commands/console.rb:8:in start' from /Users/gernot/.rvm/gems/ruby-1.9.2-p180@global/gems/railties-3.1.0.rc4/lib/rails/commands.rb:40:in<top (required)>'
from script/rails:6:in `require'

The name 'squeel' is kind of lame.

(This is opinion.)

So I was talking to the rest of my team about the new version of metawhere, which is called "squeel". The moment I mentioned the name, everyone started laughing, somewhat derisively.. My friend says, "It'll go great with HAML!"

Ok, that was kind of funny. But, honestly, being a little embarrassed about the name actually makes me slightly less likely to use it. Even though I've used metawhere and I know the quality of Ernie's code and dedication to making the plugin awesome is completely top-notch. I thought Metawhere was really a kickass name.

I strongly encourage a Metawhere 2.0... I'll probably keep using the plugin regardless of what it's called, but if I was a new user perusing options, I'd probably just pass Squeel right over because it just sounds kind of lame.

(end opinion)

is NULL

how can i check if an attribute is null?

has_many :through with :source broken (undefined method `klass' for nil:NilClass)

Hi there,

I tried using squeel today and it broke my has_many :through associations when I manually specify a :source. e.g.

has_many :memberships, :class_name => "Community::Membership", :dependent => :destroy
has_many :members, :through => :memberships, :source => :user

I don't have time to look into what's going on atm, but thought I'd post my stack trace, in case it's of interest to you.

Thanks,
iHiD

NoMethodError: undefined method `klass' for nil:NilClass
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activesupport/lib/active_support/whiny_nil.rb:48:in `method_missing'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/reflection.rb:391:in `block in source_reflection'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/reflection.rb:391:in `collect'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/reflection.rb:391:in `source_reflection'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/reflection.rb:538:in `derive_class_name'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/reflection.rb:128:in `class_name'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/squeel-67edfcb84837/lib/squeel/adapters/active_record/relation.rb:59:in `block in infer_association_for_relation_merge'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/squeel-67edfcb84837/lib/squeel/adapters/active_record/relation.rb:59:in `each'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/squeel-67edfcb84837/lib/squeel/adapters/active_record/relation.rb:59:in `detect'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/squeel-67edfcb84837/lib/squeel/adapters/active_record/relation.rb:59:in `infer_association_for_relation_merge'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/squeel-67edfcb84837/lib/squeel/adapters/active_record/relation.rb:46:in `merge'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/through_association.rb:17:in `block in target_scope'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/through_association.rb:16:in `each'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/through_association.rb:16:in `target_scope'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/association.rb:88:in `scoped'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/collection_proxy.rb:47:in `scoped'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/activerecord/lib/active_record/associations/collection_proxy.rb:102:in `method_missing'
    from (irb):1
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/railties/lib/rails/commands/console.rb:45:in `start'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/railties/lib/rails/commands/console.rb:8:in `start'
    from /Users/iHiD/.rvm/gems/ruby-1.9.2-p180/bundler/gems/rails-7ee15e8a0f84/railties/lib/rails/commands.rb:40:in `<top (required)>'

Complex joins are translated wrongly in PostgreSQL

Hi Ernie,
I was experiencing your good gem, and I've been able to write a quite complex query. Here's the scenario:
I've ModelA which has many ModelB
What I needed is to count B occurrences for every A item. I came up with this:

ModelA.joins{modelb}.group{modelb.modela_id}.select{"modela_id.*, count("modelbs"."modela_id") as modelb_count"}.order{"modelb_count desc"}

Which works well in sqlite and mysql as well.
In PostgreSQL it doesn't works. It gets translated this way:

SELECT modelas.*, count("modelbs"."modela_id") as modelbs_count FROM "modelas" INNER JOIN "modelbs" ON "modelbs"."modela_id" = "modelas"."id" GROUP BY "modelbs"."modela_id" ORDER BY modelbs_count desc

One "right" translations, according with [this][http://stackoverflow.com/questions/1769361/postgresql-group-by-different-from-mysql] should be:

SELECT modelas.*, countt from modelas, (SELECT modelbs.modela_id, count(modelbs.modela_id) as countt FROM modelbs GROUP BY modelbs.modela_id) modelbs where modelas.id=modelbs.modela_id ORDER BY countt DESC ;

Another solution should be to join non related data, this way (WARNING: not working):

ModelA.joins{Modelbs.select{"modelbs.modela_id, count(modelbs.modela_id) as countt"}.group{modelbs.modela_id}.order("countt DESC")}

But this, of course, fails due non existance of ModelB for ModelA.

I'll provide an step-by-step to reproduce the issue asap.

Rewriting meta_where to squeel

Hey ernie,

I am trying to rewrite some of my old meta_where queries to squeel queries as part of an upgrade to Rails 3.1 but I am running into problems:

Here is a query I am trying to rewrite:

account.pages.where(parent_id: parent_id_was, :id.not_eq => id, :position.gt => position).each do |page|
  page.decrement!(:position)
end

As you can see it is a mix of the new Ruby 1.9 syntax and the old meta_where (which can't be used with the 1.9 syntax).

I tried the following but it gives me an error saying there is no column parent_id_was, which obviously is not what I want. I am trying to use Dirty objects

any idea what I am doing wrong?

account.pages.where{(parent_id.eq parent_id_was) & (id.not_eq id) & (position.gt position)}.each do |page|
  page.decrement!(:position)
end
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: pages.parent_id_was: SELECT "pages".* FROM "pages" INNER JOIN "themes" ON "pages"."theme_id" = "themes"."id" WHERE "themes"."account_id" = 1 AND (("pages"."parent_id" = "pages"."parent_id_was" AND "pages"."id" != "pages"."id" AND "pages"."position" > "pages"."position")) ORDER BY position ASC):
  app/models/page.rb:240:in `update_positions'
  app/controllers/pages_controller.rb:41:in `update'

undefined method `|' for Squeel::Nodes::Function

Hi,

I do some calculations and tests on dates and postgresql provides functions for all that, like "date_le(date1, date2)" which all in all does a "date1 < date2" but allows you to bypass some casting. (Like using 'now' which will be casted to a date in the first case but won't in the second case.)

So, I tried :

where{(timestamptz_ge(date_creation, timestamptz_mi_interval('now', '10 hour'))) | (date_le(date(date_modif), date_mii('now', 7)))}

which I'd love it to generate :

((timestamptz_ge("domaine_handle"."date_creation", timestamptz_mi_interval('now', '10 hour')) OR date_le(date("domaine_handle"."date_modif"), date_mii('now', 7))))

but right now, well, does not work :-)

Support Postgres casting functions

Postgres casting functions come in two flavors, to my knowledge. The first is for general typecasting/coercion, and uses either a postfix notation (literal_value_or_variable::new_type) or a function-call style (CAST(literal_value_or_variable AS new_type)). The second is for time/date/interval typecasting/coercion, and uses a prefix notation: interval '1 week'; this notation cannot include parentheses.

Maybe I missed something, but it looks like its currently impossible to capture either of these function types in Squeel.

Here are some docs on CAST:
http://www.postgresql.org/docs/8.0/static/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS

Here are docs on time/date/interval:
http://www.postgresql.org/docs/8.0/static/datatype-datetime.html

Self referential many to many join with aliased keys

Hey Ernie,

First, thanks for the awesome gem! It's helping to make ActiveRecord more bearable after working with DataMapper for a while.

After adding squeel to one of my projects I'm getting an undefined method 'class_name' for nil:NilClass error on a self referential many to many join with aliased keys.

Its coming from Squeel::Adapters::ActiveRecord::Relation#infer_association_for_relation_merge squeel-0.8.5/lib/squeel/adapters/active_record/relation.rb:59 which is calling .class_name on a non-existent class target.

Here's the codz.

class Product < ActiveRecord::Base
  has_many  :product_relations, :foreign_key => :source_id
  has_many  :products, :through => :product_relations, :source => :target
end
class ProductRelation < ActiveRecord::Base
  belongs_to :source, :class_name => 'Product'
  belongs_to :target, :class_name => 'Product'
end
class CreateProductRelations < ActiveRecord::Migration
  def change
    create_table :product_relations do |t|
      t.integer :source_id, :null => false
      t.integer :target_id, :null => false
    end
  end
end

The error occurs when calling @product.products.

I wish I had the time to get stuck into this but I don't

inner join mentioned twice in acts-as-taggable-on, blows up postgres

hey Ernie,

Just had a bit of a mind bender before tracking this down. Was sure it was a rails edge thing, but then created a new app and things were fine there.

When I include squeel in a postgres/rails edge project, and try to use acts-as-taggable-on, I get the following

"taggable_type" = 'Board' AND (taggings.context = 'interests' AND taggings.tagger_id IS NULL)
ActiveRecord::StatementInvalid: PGError: ERROR: table name "taggings" specified more than once
: SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "taggings"."tag_id" = "tags"."id" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" IS NULL AND "taggings"."taggable_type" = 'Board' AND (taggings.context = 'interests' AND taggings.tagger_id IS NULL)

without squeel it's normal:

ActsAsTaggableOn::Tag Load (5.1ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" IS NULL AND "taggings"."taggable_type" = 'Board' AND (taggings.context = 'interests' AND taggings.tagger_id IS NULL)
=> []
ree-1.8.7-2011.03 :005 > exit

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.