-
Notifications
You must be signed in to change notification settings - Fork 652
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
User table refactor 2 #11531
User table refactor 2 #11531
Conversation
template: '<div class="loader" original-title=""></div>', | ||
options: { display: true, x: 20, y: 150 }, | ||
) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
end at 33, 4 is not aligned with def at 26, 3.
end | ||
|
||
def build_loader_overlay(order) | ||
Carto::Overlay.new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use 2 (not 3) spaces for indentation.
) | ||
end | ||
|
||
def build_loader_overlay(order) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation detected.
app/models/carto/map.rb
Outdated
longitude_size = bounds[:maxx] - bounds[:minx] | ||
|
||
# Don't touch zoom if the table is empty or has no bounds | ||
return if longitude_size == 0 && latitude_size == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use longitude_size.zero? instead of longitude_size == 0.
Use latitude_size.zero? instead of latitude_size == 0.
WIP: acceptance suggestions: After the creation + ghost tables...
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done some comments, most of them minor. Looks great! I haven't reviewed table_spec yet, though. Italics are just notes for easier tracking.
@@ -0,0 +1,73 @@ | |||
module Carto | |||
module LayerFactory | |||
def build_default_base_layer(user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is a direct translation of ModelFactories::LayerFactory#get_default_base_layer
.
Carto::Layer.new(options) | ||
end | ||
|
||
def build_default_labels_layer(base_layer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is a direct translation of ModelFactories::LayerFactory#get_default_labels_layer
.
) | ||
end | ||
|
||
def build_data_layer(user_table) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is a translation of ModelFactories::LayerFactory#get_default_data_layer
.
Nice refactor of method parameters 👌
|
||
def build_data_layer(user_table) | ||
user = user_table.user | ||
geometry_type = user_table.service.the_geom_type || 'geometry' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining user_table.the_geom_type
would avoid coupling to user_table.service
. The convention of returning 'geometry'
by default probably belongs there, too.
@@ -0,0 +1,73 @@ | |||
module Carto | |||
module LayerFactory | |||
def build_default_base_layer(user) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: as these are factories, maybe you can omit build_
method prefix, since all factory methods should be meant for building.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added build_
as opposed to create_
, i.e: this doesn't persist the visualization. Also, since this is amixin mant to be included, I thought it best to have names less likely to cause collisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, good point 👍 I'd like keeping the convention of never persisting implicitly, but we're not there yet.
app/models/table.rb
Outdated
else | ||
@user_table = args[:user_table] | ||
end | ||
# TODO: this probably makes sense only if user_table is not passed as argument | ||
@user_table.set_service(self) | ||
end | ||
|
||
def model_class | ||
::UserTable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anticipation of future replacement? Add a comment, in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing purposes, really. It's so we can run service tests against both models (by stubbing this). I'll add a comment.
user_table.unstub(:create_canonical_visualization) | ||
end | ||
|
||
trait :with_db_table do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the default should be "with_db_table", as it's the most accurate behaviour, don't you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That really increases the testing time and breaks some tests. Long story short, this is called from create_full_visualization
which works with Carto::User
. And a lot of the newer tests create users without databases, so trying to create the physical table broke them.
So I decided to keep the current behaviour by default. My personal trade-off would be to add with_canonical_visualization
by default, as that is more accurate than trying to create it in the test (and less repetitive). I don't like creating the table by default, as that forces to create user database as well, it's not needed in most cases, and it's slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's follow that approach, makes sense 👍
app/models/carto/user_table.rb
Outdated
def self.column_defaults | ||
# AR sets privacy = 0 (private) by default, taken from the DB. We want it to be `nil` | ||
# so the `before_validation` hook sets an appropriate privacy based on the table owner | ||
super.merge("privacy" => nil) | ||
end | ||
|
||
attr_accessible :privacy, :tags, :description |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is related to your comment about mass assignment blocking, isn't it? Maybe it should be a comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll try to explain it here. It's tricky to explain exactly, but at least something to raise awareness of the issue can be nice.
@@ -10,7 +10,11 @@ | |||
|
|||
@user = FactoryGirl.create(:carto_user) | |||
@carto_user = @user | |||
@user_table = Carto::UserTable.new(user: @user, name: unique_name('user_table')) | |||
|
|||
@user_table = Carto::UserTable.new |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now forbidden because of attr_accessible
, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup.
@@ -23,7 +27,9 @@ | |||
|
|||
it_behaves_like 'user table models' do | |||
def build_user_table(attrs = {}) | |||
Carto::UserTable.new(attrs) | |||
ut = Carto::UserTable.new | |||
ut.assign_attributes(attrs, without_protection: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also forbidden because of attr_accessible
, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, I had to use extreme measures to make it work. The named parameter is pretty nice for that.
Ready for second round. I'd recommend checking the commit list to see what's changed (or even reviewing per-commit, I tried to make them pretty self-contained this time :D). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I salute you for the CARTO public service you have done ❤️
Really great extraction. Very satisfying to see bad practices bite the dust in pro of better extraction and orthogonalisation. Very good work.
I've left some comments I'd like addressed. Most are design comments. Let's debate!
build_zoom_overlay(6), | ||
build_loader_overlay(8) | ||
] | ||
overlays << build_logo_overlay(9) unless user.has_feature_flag?('disabled_cartodb_logo') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should have used configuration for this!
Carto::Overlay.new( | ||
order: order, | ||
type: "logo", | ||
template: '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a default in the model, so there's no need to pass an empty string in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look and see if there is any problem in this being a default, or we are relying on it being nil in some other place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be set to null from the frontend (via controllers) and .carto
restores. Don't know if it makes sense, but I do not feel confident changing behaviour for those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that doing what I proposed is best in this case, it's your call!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But there are some parts of the code that want to save nil
instead of empty string!
Carto::Overlay.new( | ||
order: order, | ||
type: 'search', | ||
template: '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
class VisualizationFactory | ||
def self.build_canonical_visualization(user_table) | ||
kind = user_table.raster? ? Carto::Visualization::KIND_RASTER : Carto::Visualization::KIND_GEOM | ||
esv = user_table.external_source_visualization |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole PR is beautiful. I'd stay away from acronyms, it's the only thing making my eyes jump to the top of this file every so often to check what it meant. Don't break the graceful flow downwards 😢
app/models/carto/map.rb
Outdated
@@ -3,7 +3,55 @@ | |||
require_relative '../../helpers/bounding_box_helper' | |||
require_relative './carto_json_serializer' | |||
|
|||
module Carto::MapBoundaries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, let's separate classes and modules to separate files. It's off-putting to come into a model and scroll through a whole module!
@@ -132,6 +156,10 @@ def permission | |||
visualization.permission if visualization | |||
end | |||
|
|||
def external_source_visualization | |||
data_import.try(:external_data_imports).try(:first).try(:external_source).try(:visualization) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General comment totally ignorable:
I know I've contributed to it, but I have mixed feelings about the .try
explosion.
It's good when you can't trust the data structure to be populated but let's make sure we design new data structures so that they don't require so much trying
def set_random_id | ||
# This should be done with a DB default | ||
self.id ||= random_uuid | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an ActiveRecord
! Can't we make the db do this now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but that requires a DB migration on the visualization table, and you know the current situation around that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😞
app/models/carto/visualization.rb
Outdated
@@ -82,6 +84,11 @@ class Carto::Visualization < ActiveRecord::Base | |||
validates :version, presence: true | |||
|
|||
before_validation :set_default_version | |||
before_create :set_random_id, :set_default_permission | |||
|
|||
# INFO: workaround for array saves not working |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please elaborate, it's really weird! The comment needs more info :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an old comment, I just moved it :D
Basically, there is a bug in activerecord-postgresql-array
so it doesn't work on inserts, but it does work with updates. I'll expand a bit on the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make the comment better then, explaining the two methods and the delay
part. It's worth the explanation IMHO.
# This is here just for testing purposes (being able to test this service against both models) | ||
def model_class | ||
::UserTable | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know the pain you went through with this, but I feel that code shouldn't be changed to make a test framework work.
How about adding a TODO:
in the comment and packing some info on when this can be removed? (i.e.: remove when on rspec x.y.z)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't have to do with rspec at all. It's just so I can stub the class used by the service, so I can run the same specs against the two models.
The thing is, most tests related to user table are used through this service, so this allows me to select which model to user (which is used in the initializers).
This will be removed when we delete ::UserTable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Umm I'd favor some .class.parents
magic in tests instead then. I still maintain that code shouldn't change to fit tests better.
Let's leverage anyway. @juanignaciosl what's your take?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How could you? I mean, the problem is that when the test does ::Table.new
, this instantiates a ::UserTable
. I'd like for it to be a Carto::UserTable
so this service calls the new model for metadata persistence. I really could not find any way to do this without changing production code 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there something to be done with .ancestors
o something like .superclass
(although no inheritance here)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could help in order to get the model and do something different in the test depending on it. But the problem is that we want this model (see initialize above) to instantiate different model classes, so we have to tell this service, which model class to instantiate.
user_table.unstub(:create_canonical_visualization) | ||
end | ||
|
||
trait :with_db_table do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice ❤️
I fixed some things. Pending discussion:
|
Tests are 🍏. Just waiting for discussion on the topics. IMHO:
Anyway, 👍 or 👎 |
@gfiorav 's call, isn't it? Let me know if I should do anything else. |
At the very least, I'd move the module to its file. I've learned about Carto::Layer and think about it evey time I open it! |
Extracted map common module. I started a new directory ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a comment!
@@ -0,0 +1,45 @@ | |||
module Carto::MapBoundaries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this folder here too much :D
It isn't a model, and it's part of our carto code, so I'd say lib/carto/map_boundaries.rb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree in both counts 😆
- It's not a model, but it's an obligatory part of a model. This module cannot be used anywhere else than the model (it uses model methods), and the model depends on this module (the model calls methods on this module).
- Also, we use
carto
for things of the newer models. But this is not the case, this is common for both the old and new models. But well, that's the lesser part.
I really think we should keep this somewhere close to the models as it is so tightly related with them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note. We should consider deleting this module and copying the code it contains to the model when we finish the refactor, because at that point, it will be a module used in only one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, I guess there is the discrepancy: I think it's nice to have this out of the model in any case. Service like. Do what you think is best 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's why I mentioned it, I thought it might be the case. There is already kind of a service in here: BoundingBoxHelper
which does part of the job. We could refactor some of this code into that service, but will still need a part of the code to copy the result from that service to the map model.
Anyway, I don't like touching the map code in the user table refactor anyway, so I prefer to leave it as is, and maybe get to it later. We have a limited time to refactor, and ::UserTable
is the one that being bad to us.
As a reminder that this is not the cause, I've detected #11571 during this acceptance. |
"Do not merge" because this is not a Friday deploy, but it's ready 👍 |
\o/. Modified issue title for extra visibility, you can never be too careful. |
Well, I'm still assigned ;-) |
Sure! That's another great way to ensure it 💖 |
#11522
Risk meter: 🔴🔴🔴⚪️⚪️
Adding creation callbacks to
Carto::UserTable
, banning mass assignment fromCarto::UserTable
. But they should have no impact since we are not creating new tables using the AR model in production code. Moving boundaries code in map models.In this PR I worked on the table creation process. Main changes:
app/model_factories/carto/*
)Carto::UserTable
(ported from the Sequel model). This is needed to make sure the name is set through the Table service, which will make checks to ensure the name is valid and available. I don't like it, but it's quite difficult to change due to hidden dependencies and we have enough in our plate just with the ORM change.Also I did a lot of changes to tests to facilitate development. I split the tests for the service in two sets, the ones which work with both models right now, and the ones which work with just the old user table model. To continue the refactor, you can enable the full suite for both models at the bottom of
table_spec.rb
and see what is red. This is the bulk of PR lines (moving a bunch of stuff around) and it is only for ease of testing. We can consider reverting this.Missing: checking named maps updates and friends. They probably work due to some model hooks, and we can check later in the next steps (implement model update and deletion).
Suggested next steps: The main thing broking tests right now is table renaming. Mainly because the tests in
table_spec
can depend on the previous specs, so if renaming fails, it causes naming conflicts for future examples. 😞Acceptance: similar to #11508