Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change .all and has_many to return *Collection object #176

Merged
merged 3 commits into from
Apr 26, 2018

Conversation

Adam-Stomski
Copy link
Contributor

@Adam-Stomski Adam-Stomski commented Apr 5, 2018

My proposal for lazy loaded collections.

Now using .all method queries database immediately, after this change it would return a Granite::ORM::Collection(Model) which forwards missing methods to the array of records.

# before:
Klass.all # queries database [Klass1, Klass2]

# after:
Klass.all # does not query => Granite::ORM::Collection(Klass)
Klass.all.each do |klass| # queries db
  # (...)
end

Now using has_many association methods, loads all related records from the database. After this change, it would return a Granite::ORM::AssociationCollection(Owner, Target). This allows easy extensions on associations (for example #121 should be easy to implement as association.delete_all). Included find_by and all as an example for this.

# before:
student.klasss # => queries database [Klass1, Klass2]

# after:
student.klasss # does not query => Granite::ORM::AssociationCollection(Student, Klass)
student.klasss.each do |klass| # queries db
  # (...)
end

new methods

klasses = student.klasss.all("AND klasss.type = ? LIMIT ?", ["maths", 10]) # => Granite::ORM::Collection(Klass)
klasses.map(&.name) # queries db

klass = student.klasss.find_by(:name, "Klass A1")
klass = student.klasss.find_by!(:name, "Klass A1")
klass = student.klasss.find(1)
klass = student.klasss.find!(1)

I didn't want any new query syntax, just reuse what Granite already has, making associations less dumb, and prevent unnecessary db queries. Also didn't add any docs for now, would like to hear you opinion on this first :)

@robacarp
Copy link
Member

robacarp commented Apr 6, 2018

Just looking at the doc you have here in the pull, I think it’s a good idea. Granite is currently pretty chatty and I’d like to see that cleaned up however possible.

I haven’t looked at the code here yet, but I suspect I’ve implemented something vaguely similar in the query builder (so I’m obviously onboard with the idea).

Is the idea here to prevent queries from being run on variables that are never used?

enrollment3.student = student
enrollment3.save

klasses = student.klasss.all("AND klasss.name = ? ORDER BY klasss.id DESC", ["Test class X"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax is an improvement for sure

@Adam-Stomski
Copy link
Contributor Author

@robacarp I want to improve way working with relations. Since I needed something in the middle to provide methods like student.teachers.all I thought that it would be cool to have consistent API with lazy load using all method without relation too.

I believe that preventing queries from unused variables is a very good thing. I've seen a lot of code written with passing down collection through few methods to not be used because of one condition at the end.

Since these collections forwards missing to the array the only breaking change there is returned type. I believe this is a small problem compared to possibilities it gives and "free" optimization

@eliasjpr
Copy link
Contributor

eliasjpr commented Apr 6, 2018

@faustinoaq we should label this with a breaking change label.

@Adam-Stomski this is nice addition 👍

enrollment1.student = student
enrollment1.klass = klass1
enrollment1.save
klass = teacher.klasss.find_by!(:name, "Test class with different name").not_nil!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need the not_nil! since you are using the find_by!

enrollment3.klass = klass2
enrollment3.student = unrelated_student
enrollment3.save
klass = teacher.klasss.find!(klass1.id).not_nil!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

enrollment3.student = student
enrollment3.save

klass = student.klasss.find_by!(:name, "Test class with different name").not_nil!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

enrollment3.student = student
enrollment3.save

klass = student.klasss.find!(klass1.id).not_nil!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here

Copy link
Member

@drujensen drujensen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent addition. Thanks! 💯

@robacarp we will need to update the query dsl pr

@drujensen
Copy link
Member

@robacarp need another approval when you get a chance

Copy link
Member

@robacarp robacarp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for removing the redundant .not_nil!

@drujensen drujensen merged commit e700242 into amberframework:master Apr 26, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants