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

Array param support for operations #847

Merged
merged 5 commits into from
Jul 14, 2022
Merged

Conversation

jwoertink
Copy link
Member

@jwoertink jwoertink commented Jun 18, 2022

Fixes #408

This PR adds in the ability to pass arrays through params in to operations. Prior to this, you could only set array values through named args, or manually by splitting a string or something like that.

Now you can do this:

# post:tags[]=Crystal&post:tags[]=Lucky&post:title=Woot!&post:content=beepboop
SavePost.create(params) do |op, post|
  # ...
end

In doing this, there was quite a bit that needed some refactoring. One major breaking change is that Avram::Params now requires that all Hash values are Array(String) instead of String.

# before
Avram::Params.new({"key" => "value"})

# after
Avram::Params.new({"key" => ["value"]})

Commit 15d7741 causes this error when I run specs:

This is the spec that causes the crash.
spec/avram/operations/define_attribute_spec.cr:227

Invalid memory access (signal 11) at address 0x18
[0x5590191bc336] *Exception::CallStack::print_backtrace:Nil +118 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559018f9fd4a] ~procProc(Int32, Pointer(LibC::SiginfoT), Pointer(Void), Nil) +330 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x7f9fb73dc520] ?? +140323950806304 in /lib/x86_64-linux-gnu/libc.so.6
[0x5590195a7717] *Avram::Params+ +39 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590197ae1df] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes +143 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590197aeed6] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes +22 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590197aeeb6] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes#set_attributes<Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing, Avram::Nothing>:Nil +6 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590197b5121] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes#initialize<Avram::UploadParams>:Nil +257 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590197b500d] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes::new<Avram::UploadParams>:/home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes +125 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019864c4e] */home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::upload_save_operation<Hash(String, Avram::UploadedFile)>:/home/jeremy/Development/lucky_org/avram/spec/avram/operations/define_attribute_spec.cr::SaveOperationWithAttributes +14 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590191427e9] ~procProc(Nil) +89 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019408864] *Spec::Example#internal_run<Time::Span, Proc(Nil)>:(Array(Spec::Result) | Nil) +244 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559018fa294e] ~procProc(Nil) +30 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901940b121] *Spec::Example::Procsy#run:Nil +49 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559018fa29d2] ~procProc(Nil) +98 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901940b121] *Spec::Example::Procsy#run:Nil +49 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019613b08] *Avram::SpecHelper::wrap_spec_in_transaction<Spec::Example::Procsy, TestDatabase.class>:Nil +568 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901911213e] ~procProc(Spec::Example::Procsy, Nil) +78 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901940af7a] *Spec::Context+ +442 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901940adb6] *Spec::Context+ +118 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x55901940ad2d] *Spec::Context+ +77 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590194071b2] *Spec::ExampleGroup#run_around_each_hooks<Spec::Example::Procsy>:Bool +290 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590194086a8] *Spec::Example#run:Nil +968 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019407769] *Spec::ExampleGroup +137 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019407616] *Spec::ExampleGroup#run:Nil +422 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590193b7e5b] *Spec::RootContext +187 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x5590193b7d96] *Spec::RootContext#run:Nil +6 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559018fa2659] ~procProc(Int32, (Exception | Nil), Nil) +57 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019351e16] *Crystal::AtExitHandlers::run<Int32, (Exception+ | Nil)>:Int32 +166 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019ac4b23] *Crystal::exit<Int32, (Exception+ | Nil)>:Int32 +35 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559019ac4a8f] *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 +111 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x559018f92f46] main +6 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x7f9fb73c3fd0] ?? +140323950706640 in /lib/x86_64-linux-gnu/libc.so.6
[0x7f9fb73c407d] __libc_start_main +125 in /lib/x86_64-linux-gnu/libc.so.6
[0x559018f7ee85] _start +37 in /home/jeremy/.cache/crystal/crystal-run-spec.tmp
[0x0] ???

@jwoertink jwoertink changed the title [WIP] working on array param support in operations Array param support for operations Jul 4, 2022
@jwoertink jwoertink marked this pull request as ready for review July 4, 2022 22:25
@jwoertink jwoertink added the BREAKING CHANGE This will cause a breaking change label Jul 4, 2022
@@ -1,6 +1,6 @@
require "../../spec_helper"

include LazyLoadHelpers
Copy link
Member Author

Choose a reason for hiding this comment

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

This wasn't being used.

@@ -4,6 +4,7 @@ class Avram::UploadParams < Avram::Params
@uploads : Hash(String, Avram::UploadedFile) = {} of String => Avram::UploadedFile

def initialize(@uploads)
@hash = {} of String => Array(String)
Copy link
Member Author

Choose a reason for hiding this comment

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

The specs wouldn't compile without this... How it was working before is magic....

# This module creates methods for each column in a model that map
# to an `Avram::Attribute` as well as methods that fill those attributes
# with values that comes from params.
module Avram::AddColumnAttributes
Copy link
Member Author

Choose a reason for hiding this comment

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

This code was in both SaveOperation and DeleteOperation, but seemed to have a few slight differences. One being that it was generating a permitted_params method for every column, but that method never did anything at the compiler level. So this becomes a small refactor since I had to change how things are checked.

single_values = @params.nested(self.class.param_key).reject {|k,v| k.ends_with?("[]")}
array_values = @params.nested_arrays?(self.class.param_key) || {} of String => Array(String)
new_params = single_values.merge(array_values)
new_params.select(@@permitted_param_keys)
Copy link
Member Author

Choose a reason for hiding this comment

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

This will use some autocast magic which becomes Hash(String, Array(String) | String). Maybe I should just add the return type?

Comment on lines +15 to +17
set_{{ attribute[:name] }}_from_param(value.as(Array(String))) if key == {{ attribute[:name].stringify }}
{% else %}
set_{{ attribute[:name] }}_from_param(value.as(String)) if key == {{ attribute[:name].stringify }}
Copy link
Member Author

Choose a reason for hiding this comment

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

If we don't cast these here, then typeof(value) is Array(String) | String, and the compiler starts to get confused

Comment on lines +73 to +99
{% if attribute[:type].is_a?(Generic) %}
def set_{{ attribute[:name] }}_from_param(_value : Array(String))
parse_result = {{ attribute[:type] }}.adapter.parse(_value)

if parse_result.is_a? Avram::Type::SuccessfulCast
{{ attribute[:name] }}.value = parse_result.value.as({{ attribute[:type] }})
else
{{ attribute[:name] }}.add_error "is invalid"
end
end
{% else %}
def set_{{ attribute[:name] }}_from_param(_value)
# In nilable types, `nil` is ok, and non-nilable types we will get the
# "is required" error.
if _value.blank?
{{ attribute[:name] }}.value = nil
return
end

parse_result = {{ attribute[:type] }}.adapter.parse(_value)

if parse_result.is_a? Avram::Type::SuccessfulCast
{{ attribute[:name] }}.value = parse_result.value.as({{ attribute[:type] }})
else
{{ attribute[:name] }}.add_error "is invalid"
end
end
Copy link
Member Author

Choose a reason for hiding this comment

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

This was just 1 method before that did a quick check if the type was Generic or not before parsing. However, I think there was a bug doing it that way. We call if _value.blank? because if it's an empty string, treat it as nil. But in the case of an empty array, that could be a very valid value, and shouldn't be treated as nil.

Now, if we still want that same functionality (treating [] as nil), then I can add that back in, but I think that may have been a mistake.

attribute brand : String
end

describe "OperationParams" do
Copy link
Member Author

Choose a reason for hiding this comment

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

This spec file is intended to utilize Lucky directly now that Lucky is a dependency of Avram. We should be able to test real legit params instead of stubbing out params all over.

@jwoertink
Copy link
Member Author

I'm going to merge this in so I can move on to a few other things, but if anyone comes across issues, let me know before next release, or open up an issue.

@jwoertink jwoertink merged commit 781609b into main Jul 14, 2022
@jwoertink jwoertink deleted the operation_array_attributes branch July 14, 2022 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BREAKING CHANGE This will cause a breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for (virtual) array attributes
1 participant