Live data from Hacker News

Arel merge, rails' hidden gem

benhoskin.gs

1–10 of 17 posts

Re: Arel merge, rails' hidden gem

#2
Comments aren't available on the article, so a couple of quick clarifications here:

Nitpicky, but ActiveRecord::Relation is not ARel. ARel is the relational algebra library that underpins ActiveRecord (>=3.0). See http://erniemiller.org/2010/05/11/activerecord-relation-vs-a... for more info.

The specific example he used to demonstrate worked transparently because of the has_many :through relationship for users on Article, which requires the collaborations table to filter the users, so joins it. Otherwise, you would need to do the join yourself before merging the scope, and things get messy pretty quickly, especially if you end up with table aliases (which the merged relation knows nothing about -- it will still query against the collaborations table).

I added "sifters" to Squeel (http://github.com/ernie/squeel) in order to address the need for a set of reusable conditions on a specific model that could work through an association. I'm not saying Squeel is the solution you're definitely looking for, but I just want to let people know about the things they'll need to look out for when using Relation#merge.

Re: Arel merge, rails' hidden gem

#3
I've never been a fan of Rails' hieroglyphics. "The query is the one you’d hope for" - why do we need to "hope", when the ORM could just allow you to use relational concepts directly ? Here is SQLAlchemy's much less exciting version of what I see here for "merge", just use a hybrid (sorry, we have more verbose config, due to explicit is better than implicit):

    from sqlalchemy import Column, Integer, String, ForeignKey, Enum
    from sqlalchemy.orm import Session, relationship, backref
    from sqlalchemy.ext.declarative import declarative_base, declared_attr
    from sqlalchemy.ext.hybrid import hybrid_property

    class Base(object):
        @declared_attr
        def __tablename__(cls):
            return cls.__name__.lower()

    Base = declarative_base(cls=Base)

    class SurrogatePK(object):
        id = Column(Integer, primary_key=True)

    class Article(SurrogatePK, Base):
        headline = Column(String)

        @property
        def users(self):
            return self.collaborations.join("user").with_entities(User)

    class User(SurrogatePK, Base):
        name = Column(String)

    class Collaboration(Base):
        article_id = Column(ForeignKey('article.id'),
                                primary_key=True)
        user_id = Column(ForeignKey('user.id'),
                                primary_key=True)
        role = Column(Enum('editor', 'author'))
        user = relationship("User", backref="collaborations")
        article = relationship("Article",
                    backref=backref("collaborations", lazy="dynamic"))

        @hybrid_property
        def editorial(self):
            return self.role == 'editor'

    sess = Session()

    some_article = Article(id=5)
    sess.add(some_article)

    print some_article.users.filter(Collaboration.editorial)
you get the same "one line, DRY" calling style at the end and equivalent SQL:

    SELECT "user".id AS user_id, "user".name AS user_name 
    FROM collaboration JOIN "user" ON "user".id = collaboration.user_id 
    WHERE :param_1 = collaboration.article_id AND collaboration.role = :role_1

Re: Arel merge, rails' hidden gem

#4

Comments aren't available on the article, so a couple of quick clarifications here: Nitpicky, but ActiveRecord::Relation is not ARel. ARel is the relational algebra library that underpins ActiveRecord (>=3.0). See http://erniemiller.org/2010/05/11/activerecord-relation-vs-a... for more info. The specific example he used to demonstrate worked transparently because of the has_many :through relationship for users on Art…

Good point, #merge isn't itself an arel method. I say arel because it's the core sitting behind the #merge / #where / #joins frontend -- but AR::Relation deserves credit too :)

Re: Arel merge, rails' hidden gem

#5
Here's one that's been bugging me - anyone want to take a crack at it?

    @dev_configs = DeviceConfig.
    joins("join (select device_id, max(updated_at) as max_updated_at 
    from device_configs group by device_id) dc2 
    on dc2.device_id = device_configs.device_id and   
    dc2.max_updated_at = device_configs.updated_at").
    includes("device").order("devices.updated_at desc")
Each device has many device configurations, and we want to display all devices, along with the latest device configuration, and order the whole thing by when the device was updated. The above works, but is mostly working directly in SQL, rather than with Rails.

Re: Arel merge, rails' hidden gem

#6
post #3

I've never been a fan of Rails' hieroglyphics. "The query is the one you’d hope for" - why do we need to "hope", when the ORM could just allow you to use relational concepts directly ? Here is SQLAlchemy's much less exciting version of what I see here for "merge", just use a hybrid (sorry, we have more verbose config, due to explicit is better than implicit): from sqlalchemy import Column, Integer, String, ForeignKey…

Hope was definitely a bad choice of word. By that I meant, the sort of tight query you'd hope an ORM would deliver.

Anyhow, it's a matter of taste, but what may appear at first as hieroglyphics actually is straightforward. It's just that concision here means some packed meaning and some assumed knowledge, so you have to know how to read it. In this case, that's an easy trade-off for me.

I'm not familiar with SQLAlchemy, so I find your example equally hard to read, compounded by there being much more code to spelunk through to understand.

Different strokes and all that, though. There's room for plenty of frameworks :)

Re: Arel merge, rails' hidden gem

#7
post #6
post #3

I've never been a fan of Rails' hieroglyphics. "The query is the one you’d hope for" - why do we need to "hope", when the ORM could just allow you to use relational concepts directly ? Here is SQLAlchemy's much less exciting version of what I see here for "merge", just use a hybrid (sorry, we have more verbose config, due to explicit is better than implicit): from sqlalchemy import Column, Integer, String, ForeignKey…

Hope was definitely a bad choice of word. By that I meant, the sort of tight query you'd hope an ORM would deliver. Anyhow, it's a matter of taste, but what may appear at first as hieroglyphics actually is straightforward. It's just that concision here means some packed meaning and some assumed knowledge, so you have to know how to read it. In this case, that's an easy trade-off for me. I'm not familiar with SQLAlche…

It's just a little friendly/competitive poke! Don't take it seriously, I just couldn't resist.

I mostly wanted to demonstrate that the functionality of Rails' merge() can be considered in other ways that are just as succinct.

Re: Arel merge, rails' hidden gem

#8
post #5

Here's one that's been bugging me - anyone want to take a crack at it? @dev_configs = DeviceConfig. joins("join (select device_id, max(updated_at) as max_updated_at from device_configs group by device_id) dc2 on dc2.device_id = device_configs.device_id and dc2.max_updated_at = device_configs.updated_at"). includes("device").order("devices.updated_at desc") Each device has many device configurations, and we want to di…

In stock ActiveRecord, you're not going to be able to join a derived table without dropping to SQL. :( Still, if you're looking to constrain results to only the latest, you could use the having clause (with some performance penalty).

Re: Arel merge, rails' hidden gem

#9
post #6
post #3

I've never been a fan of Rails' hieroglyphics. "The query is the one you’d hope for" - why do we need to "hope", when the ORM could just allow you to use relational concepts directly ? Here is SQLAlchemy's much less exciting version of what I see here for "merge", just use a hybrid (sorry, we have more verbose config, due to explicit is better than implicit): from sqlalchemy import Column, Integer, String, ForeignKey…

Hope was definitely a bad choice of word. By that I meant, the sort of tight query you'd hope an ORM would deliver. Anyhow, it's a matter of taste, but what may appear at first as hieroglyphics actually is straightforward. It's just that concision here means some packed meaning and some assumed knowledge, so you have to know how to read it. In this case, that's an easy trade-off for me. I'm not familiar with SQLAlche…

> I'm not familiar with SQLAlchemy, so I find your example equally hard to read, compounded by there being much more code to spelunk through to understand.

If you remove few column definition and setup code, it actually boils down to just:

    class Article(ArticleColumns):
        @property
        def users(self)
            return self.collaborations.join("user").with_entities(User)
    
    class User(UserColumns):
        pass
    
    class Collaboration(CollaborationColumns):
        user = relationship("User", backref="collaborations")
        article = relationship("Article", backref=backref("collaborations", lazy="dynamic"))
    
        @hybrid_property
        def editorial(self):
            return self.role == 'editor'
In which you can now do

    some_article.users.filter(Collaboration.editorial)
which generates similar SQL query as #merge.
Post reply on HN