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.
