Skip to content

Remove HABTM relationships#289

Closed
BenMorganIO wants to merge 36 commits into
solidusio:masterfrom
BenMorganIO:remove-habtms-in-favour-of-habtms
Closed

Remove HABTM relationships#289
BenMorganIO wants to merge 36 commits into
solidusio:masterfrom
BenMorganIO:remove-habtms-in-favour-of-habtms

Conversation

@BenMorganIO

Copy link
Copy Markdown
Contributor

Please refer to spree/spree#6627.

Comment thread core/app/models/spree/prototype.rb Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This needs to be changed here. Made a mistake with sorting my ABCs.

Comment thread core/app/helpers/spree/taxons_helper.rb Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I would prefer to move this to the model as a scope later on or in this PR to help refactor this code.

Also, this line right here was originally:

DISTINCT (spree_products.id)

Arel now generates:

DISTINCT ON (spree_products.id)

It may partially be because I'm using Arel::Nodes::DistinctOn instead of Arel::Nodes::Distinct. However, I couldn't find a way for Distinct to be able to accept a table and a column, but DistinctOn did.

@gmacdougall WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@gmacdougall For instances such as this, what are your thoughts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you mean the table rename here? I think its fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

kk

I fought the AST and the AST won…

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If anyone happens to know how to update this, it would be appreciated. :)

This caused a regression while I was updating. Maybe another day…
Comment thread core/app/models/spree/product/scopes.rb Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does anyone know how to get this to be arel friendly? I tried the Spree::Classification.are_table[:taxon_id], however I didn't realize how some of Arels internals would hiccup when #to_sym was called onto it; the usage of #to_sym is probably so that Rails can lower the amount of string allocations and know the key type.

Anywho, for those who want a deeper understanding of the problem, here it is:

When you call Foo.arel_table[:bar] you have called into the Arel::Table and then Arel hands you back this lovely Arel::Attributes::Attributes (also aliased to Arel::Attribute). This is all nice and pretty and you go "I have an Arel object back, yippie, now lets get the SQL!" and you end up doing Foo.arel_table[:bar].to_sql and you get UndefinedMethodError. Wat?

But wait, if we stop for a second and start doing some work with the code:

Foo.arel_table[:bar].lower.to_sql
# => "LOWER(\"foo\".\"bar\"")

OK, so somehow there is a reference to the table. Its there. It exists. All we did was call #lower and then it just wrapped it. It turns out that you actually need to setup the code so that there's a reference to a node. Why? Cause that's where #to_sql is defined.

Its unfortunate that the docs for #to_sql start with FIX ME and its not because people want Foo.arel_table[:bar].to_sql but because of the AST traversal to find a relation.

If we want to be able to pump a string out, this is what the code started to look life for me after a half hour of hacking:

collector = Arel::Collectors::SQLString.new
collector = ActiveRecord::Base.connection.visitor.accept Foor.arel_table[:bar], collector
collector.value
# => "\"foo\".\"bar\""

TBH, if we did a lot of this and changed up the app to use Arel in a lot more spots, we could start (badly) supporting more DBs than just MySQL, PGSQL, and SQLite. But, hey, lets just stick to doing the Rails-Way™.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Give this a try:

      add_search_scope :in_taxon do |taxon|
        joins(:classifications).
        where(Spree::Classification.table_name => {
          taxon_id: taxon.self_and_descendants
        }).
        order(Spree::Classification.arel_table[:position].asc)
      end

Comparison:

Before:

[1] pry(main)> Spree::Product.in_taxon Spree::Taxon.find_by!(permalink: "categories/bags")
  Spree::Taxon Load (0.2ms)  SELECT  "spree_taxons".* FROM "spree_taxons" WHERE "spree_taxons"."permalink" = ? LIMIT 1  [["permalink", "categories/bags"]]
   (0.2ms)  SELECT "spree_taxons"."id" FROM "spree_taxons" WHERE ("spree_taxons"."lft" >= 2) AND ("spree_taxons"."lft" < 3)  ORDER BY "spree_taxons"."lft"
=>   SQL (0.3ms)  SELECT "spree_products"."id" AS t0_r0, "spree_products"."name" AS t0_r1, "spree_products"."description" AS t0_r2, "spree_products"."available_on" AS t0_r3, "spree_products"."deleted_at" AS t0_r4, "spree_products"."slug" AS t0_r5, "spree_products"."meta_description" AS t0_r6, "spree_products"."meta_keywords" AS t0_r7, "spree_products"."tax_category_id" AS t0_r8, "spree_products"."shipping_category_id" AS t0_r9, "spree_products"."created_at" AS t0_r10, "spree_products"."updated_at" AS t0_r11, "spree_products"."promotionable" AS t0_r12, "spree_products"."meta_title" AS t0_r13, "spree_classifications"."product_id" AS t1_r0, "spree_classifications"."taxon_id" AS t1_r1, "spree_classifications"."id" AS t1_r2, "spree_classifications"."position" AS t1_r3 FROM "spree_products" LEFT OUTER JOIN "spree_classifications" ON "spree_classifications"."product_id" = "spree_products"."id" WHERE "spree_products"."deleted_at" IS NULL AND "spree_classifications"."taxon_id" = 3  ORDER BY spree_classifications.position ASC

After:

[2] pry(main)> Spree::Product.in_taxon Spree::Taxon.find_by!(permalink: "categories/bags")
  Spree::Taxon Load (0.4ms)  SELECT  "spree_taxons".* FROM "spree_taxons" WHERE "spree_taxons"."permalink" = ? LIMIT 1  [["permalink", "categories/bags"]]
=>   Spree::Product Load (0.4ms)  SELECT "spree_products".* FROM "spree_products" INNER JOIN "spree_classifications" ON "spree_classifications"."product_id" = "spree_products"."id" WHERE "spree_products"."deleted_at" IS NULL AND "spree_classifications"."taxon_id" IN (SELECT "spree_taxons"."id" FROM "spree_taxons" WHERE ("spree_taxons"."lft" >= 2) AND ("spree_taxons"."lft" < 3)  ORDER BY "spree_taxons"."lft")  ORDER BY "spree_classifications"."position" ASC

Query is simpler and easier to understand, plus we do it all in one shot query (with a subselect) rather than two. It functions a bit differently internally but should produce the intended results.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

😍

@BenMorganIO

Copy link
Copy Markdown
Contributor Author

I think this ready for another round of code review :)

@magnusvk

Copy link
Copy Markdown
Contributor

Thanks for splitting out the migrations. This still doesn't allow me to deploy this one at a time, though. I was hoping for there to be one commit for each HABTM that is removed so that I can deploy and go live with each intermediary step. So that would require a significant history rewrite but I don't feel good deploying this without that.

@BenMorganIO

Copy link
Copy Markdown
Contributor Author

@magnusvk Yeah, the cherry-picking will be very, picky... I could separate this into several PRs?

@magnusvk

Copy link
Copy Markdown
Contributor

I think that would probably be optimal, yeah.

@BenMorganIO

Copy link
Copy Markdown
Contributor Author

KK, I'll cherry-pick some of the commits out.

@magnusvk

Copy link
Copy Markdown
Contributor

Given that this isn't a ton of code, maybe it makes more sense to just manually pick the code over one HABTM association at a time? (Rather than using git.) I mean, either way works for me of course, but that's probably what I would try here.

@magnusvk

Copy link
Copy Markdown
Contributor

Also, thanks for doing that extra work!

@BenMorganIO

Copy link
Copy Markdown
Contributor Author

Tag #317, #318

@jhawthorn jhawthorn removed this from the 1.1.0 milestone Nov 2, 2015
cbrunsdon pushed a commit to cbrunsdon/solidus that referenced this pull request Nov 18, 2015
We're working to remove HABTM relationships from the codebase for a
variety of reasons, please see solidusio#289
@tvdeyen

tvdeyen commented Nov 18, 2015

Copy link
Copy Markdown
Member

Please don't forget to also port spree/spree#6654, because these changes introduced the bug fixed with spree #6654

Thanks

cbrunsdon pushed a commit to cbrunsdon/solidus that referenced this pull request Nov 25, 2015
Another cherry-pick from solidusio#289 as we remove all the has and belongs to
many relationships from the codebase.
cbrunsdon pushed a commit to cbrunsdon/solidus that referenced this pull request Nov 25, 2015
Should be the last cherry-pick from solidusio#289 to remove HABTM's from the
codebase.
@cbrunsdon

Copy link
Copy Markdown
Contributor

I pulled out the code in this one that I cared about that hasn't already been merged in #539 and #538.

That is the last of the HABTMs! So closing this, thanks for the work @BenMorganIO

@cbrunsdon cbrunsdon closed this Nov 25, 2015
@BenMorganIO BenMorganIO deleted the remove-habtms-in-favour-of-habtms branch November 25, 2015 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants