diff --git a/spec/model_spec.cr b/spec/model_spec.cr index 814f82830..d1ab5a559 100644 --- a/spec/model_spec.cr +++ b/spec/model_spec.cr @@ -1,5 +1,7 @@ require "./spec_helper" +include LazyLoadHelpers + class NamedSpaced::Model < BaseModel table do end @@ -90,6 +92,30 @@ describe Avram::Model do user.email.to_s.should eq "foo@bar.com" end + describe "reload" do + it "can reload a model" do + user = UserBox.create &.name("Original Name") + + # Update returns a brand new user. It should have the new name + newly_updated_user = User::SaveOperation.update!(user, name: "Updated Name") + + newly_updated_user.name.should eq("Updated Name") + # The original user is not modified + user.name.should eq("Original Name") + # So we reload it to get the new goodies + user.reload.name.should eq("Updated Name") + end + + it "can reload a model with a yielded query" do + with_lazy_load(enabled: false) do + post = PostBox.create + + # If `preload_tags` doesn't work this will raise + post.reload(&.preload_tags).tags.should be_empty + end + end + end + it "sets up simple methods for equality" do query = QueryMe::BaseQuery.new.email("foo@bar.com").age(30) diff --git a/src/avram/base_query_template.cr b/src/avram/base_query_template.cr index e2dc562f4..9ddefe3d7 100644 --- a/src/avram/base_query_template.cr +++ b/src/avram/base_query_template.cr @@ -7,6 +7,10 @@ class Avram::BaseQueryTemplate {{ type }}.database end + def query_class + + end + @@table_name = :{{ table_name }} @@schema_class = {{ type }} diff --git a/src/avram/model.cr b/src/avram/model.cr index f0304f04f..b45a560e3 100644 --- a/src/avram/model.cr +++ b/src/avram/model.cr @@ -48,6 +48,67 @@ abstract class Avram::Model id.to_s end + # Reload the model with the latest information from the database + # + # This method will return a new model instance with the + # latest data from the database. Note that this does + # **note** change the original instance, so you may need to + # assign the result to a variable or work directly with the return value. + # + # Example: + # + # ```crystal + # user = SaveUser.create!(name: "Original") + # SaveUser.update!(user, name: "Updated") + # + # # Will be "Original" + # user.name + # # Will return "Updated" + # user.reload.name # Will be "Updated" + # Will still be "Original" since the 'user' is the same model instance. + # user.name + # + # Instead re-assign the variable. Now 'name' will return "Updated" since + # 'user' references the reloaded model. + # user = user.reload + # user.name + # ``` + def reload : self + base_query_class.find(id) + end + + # Same as `reload` but allows passing a block to customize the query. + # + # This is almost always used to preload additional relationships. + # + # Example: + # + # ```crystal + # user = SaveUser.create(params) + # + # # We want to display the list of articles the user has commented on, so let's # + # # preload them to avoid N+1 performance issues + # user = user.reload(&.preload_comments(CommentQuery.new.preload_article)) + # + # # Now we can safely get all the comment authors + # user.comments.map(&.article) + # ``` + # + # Note that the yielded query is the `BaseQuery` so it will not have any + # methods defined on your customized query. This is usually fine since + # typically reload only uses preloads. + # + # If you do need to do something more custom you can manually reload: + # + # ```crystal + # user = SaveUser.create!(name: "Helen") + # UserQuery.new.some_custom_preload_method.find(user.id) + # ``` + def reload : self + query = yield base_query_class.new + query.find(id) + end + macro table(table_name = nil) {% unless table_name %} {% table_name = run("../run_macros/infer_table_name.cr", @type.id) %} diff --git a/src/avram/save_operation_template.cr b/src/avram/save_operation_template.cr index 8f9f84622..9d53dae80 100644 --- a/src/avram/save_operation_template.cr +++ b/src/avram/save_operation_template.cr @@ -6,6 +6,11 @@ class Avram::SaveOperationTemplate end end + # This makes it easy for plugins and extensions to use the base SaveOperation + def base_query_class : ::{{ type }}::BaseQuery.class + ::{{ type }}::BaseQuery + end + class ::{{ type }}::SaveOperation < Avram::SaveOperation({{ type }}) {% if primary_key_type.id == UUID.id %} before_save set_uuid