That solved it. Thank you!

Andrew

> On Feb 26, 2015, at 3:34 PM, Jeremy Evans <[email protected]> wrote:
> 
> On Thursday, February 26, 2015 at 9:03:21 AM UTC-8, Andrew Burleson wrote:
> I ran into something today I'm having a really hard time figuring out:
> 
> I've got a model instance, `user`, and after fetching that user instance I'd 
> like to make it available in a "read only" state, which is to say I don't 
> want any 'downstream' code to be able to make changes to it. #freeze 
> accomplishes this. It also makes sense to be to pass a frozen copy of the 
> object off to another process, while retaining an unfrozen copy. Testing this 
> led to some unexpected results:
> 
> The relevant portion of my tests looks like this:
> DB.transaction(:rollback => :always, :auto_savepoint=>true) do
>   user = FinishSignup.call(...)
>   assert_equal u, User.order(:id).last
>   assert_equal u.properties.first, Property.order(:id).last
> end
> 
> The `FinishSignup` call creates a user and property instance. It dups the 
> user, freezes the dup, and passes this frozen dup on to some observers. The 
> function then returns the original user instance.
> 
> The test fails on the last assertion, with a stack trace like this:
> 
> RuntimeError: can't modify frozen Hash
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/model/associations.rb:2240:in
>  `load_associated_objects'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/plugins/association_proxies.rb:83:in
>  `method_missing'
> ~/project/test/services/finish_signup_test.rb:18:in `block (3 levels) in <top 
> (required)>'
> ~/project/test/test_helper.rb:31:in `block in transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:119:in
>  `_transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:100:in
>  `block in transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/connecting.rb:250:in
>  `block in synchronize'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/connection_pool/threaded.rb:98:in
>  `hold'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/connecting.rb:250:in
>  `synchronize'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:89:in
>  `transaction'
> ~/project/test/test_helper.rb:30:in `transaction'
> ~/project/test/services/finish_signup_test.rb:9:in `block (2 levels) in <top 
> (required)>'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/model/associations.rb:2240:in
>  `load_associated_objects'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/plugins/association_proxies.rb:83:in
>  `method_missing'
> ~/project/test/services/finish_signup_test.rb:18:in `block (3 levels) in <top 
> (required)>'
> ~/project/test/test_helper.rb:31:in `block in transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:119:in
>  `_transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:100:in
>  `block in transaction'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/connecting.rb:250:in
>  `block in synchronize'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/connection_pool/threaded.rb:98:in
>  `hold'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/connecting.rb:250:in
>  `synchronize'
> ~/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/sequel-4.19.0/lib/sequel/database/transactions.rb:89:in
>  `transaction'
> ~/project/test/test_helper.rb:30:in `transaction'
> ~/project/test/services/finish_signup_test.rb:9:in `block (2 levels) in <top 
> (required)>'
> 
> 
> More background, and research I did so far:
> 
> The relevant portion of the model looks like this:
> class User < Sequel::Model
>   one_to_many :properties
> end
> 
> Inside the FinishSignup call looks basically like this:
> 
> # Create User and Property
> user = User.create(...)
> property = Property.create(user: user, ...)
> trigger_event :user_created, user: user
> 
> The trigger event method is a generic method that works like this:
> def trigger_event(name,**args)
>   frozen_args = args.each.with_object({}){|(k,v),o| o[k] = v.dup.freeze}
>   send(name,**frozen_args)
> end
> 
> ^ where send actually calls the event trigger.
> 
> The reason this is most confusing is I can't duplicate it in the console. 
> I've tried every variation of this procedure I can think of, creating a user, 
> creating a property, duping the user, freezing the dup, etc., and in every 
> case I'm able to call `original_user.properties.first` with no runtime error. 
> I've tried this in the console inside and outside of transactions etc.
> 
> So, I think something else must be going on in the Sequel Internals that I'm 
> not aware of which is the reason this specific case won't work, but I'm 
> having a terrible time figuring out what it is.
> 
> Any insight you can share?
> 
> If you look at the line of code that raises the error:
> 
>   associations[name] = objs unless frozen?
> 
> It's already supposed to check if the object is frozen.  However, from your 
> description, I think the problem is that dup doesn't duplicate the 
> associations hash, when it should.  This should fix it:
> 
> class Sequel::Model
>   def initialize_copy(other)
>     super
>     @associations = @associations.dup if @associations
>     self
>   end
> end
> 
> I'll make sure that makes it into the next version.
> 
> Thanks,
> Jeremy
> 
> -- 
> You received this message because you are subscribed to a topic in the Google 
> Groups "sequel-talk" group.
> To unsubscribe from this topic, visit 
> https://groups.google.com/d/topic/sequel-talk/FPCbZCC5hZM/unsubscribe 
> <https://groups.google.com/d/topic/sequel-talk/FPCbZCC5hZM/unsubscribe>.
> To unsubscribe from this group and all its topics, send an email to 
> [email protected] 
> <mailto:[email protected]>.
> To post to this group, send email to [email protected] 
> <mailto:[email protected]>.
> Visit this group at http://groups.google.com/group/sequel-talk 
> <http://groups.google.com/group/sequel-talk>.
> For more options, visit https://groups.google.com/d/optout 
> <https://groups.google.com/d/optout>.

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sequel-talk.
For more options, visit https://groups.google.com/d/optout.

Reply via email to