apotonick / gemgem-trbrb

The Trailblazer book's example app.
http://trailblazer.to#book
137 stars 60 forks source link

In the weeds on uploads #8

Closed HuckyDucky closed 9 years ago

HuckyDucky commented 9 years ago

Hey guys. I like to use the carrierwave_direct gem to do direct uploads to S3. In this new project, I'm creating operations for an Upload model I have.

carrierwave_direct extends AR validations to offer an :is_uploaded feature.

Like all validations, I want to put that in the contract in Create operation in app/concepts/upload/crud.rb.

To make a field on a model "uploadable" with this gem, you have to add:

mount_uploader :<fieldname>, <YourUploaderClass>

to your model. I'm trying to put it in my Upload namespace in the crud.rb file, which is where it really belongs.

I'm getting an error on the validation though, and it's a screwed-up error:

/Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:479:in `load_missing_constant': A copy of #<Class:0x007fdbf6ed35f8> has been removed from the module tree but is still active! (ArgumentError)

What the bloody hell that means I don't know. Here's the code:

class Upload < ActiveRecord::Base

    mount_uploader :filename, FileUploader
    mount_uploader :log, LogUploader
    # mount_uploader :payload, PayloadUploader

    class Create < Trailblazer::Operation

      include CRUD
      model Upload, :create

      contract do
        property :filename
        property :file_type_id

        validates_presence_of :file_type_id, :filename
        validates :filename, :is_uploaded => true

      end

      def process(params)
        validate(params[:upload]) do |f|
          f.save
        end
      end

    end

    def upload_file(options = {})
      if options[:now]
        self.filename = filename.direct_fog_url(:with_path => true)
        save
      else
        FileUploader.perform_async(attributes)
      end
    end

  end

end

The error is occurring just on running a console or server. The stacktrace points to that line:

/Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:479:in `load_missing_constant': A copy of #<Class:0x007ff2f3bcc0f8> has been removed from the module tree but is still active! (ArgumentError)
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:184:in `const_missing'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activemodel-4.2.1/lib/active_model/validations/validates.rb:118:in `const_get'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activemodel-4.2.1/lib/active_model/validations/validates.rb:118:in `block in validates'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activemodel-4.2.1/lib/active_model/validations/validates.rb:113:in `each'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activemodel-4.2.1/lib/active_model/validations/validates.rb:113:in `validates'
from /Users/me/code/bumble/app/concepts/upload/crud.rb:17:in `block in <class:Create>'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/operation.rb:46:in `class_eval'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/operation.rb:46:in `contract'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/operation/crud.rb:77:in `contract'
from /Users/me/code/bumble/app/concepts/upload/crud.rb:12:in `<class:Create>'
from /Users/me/code/bumble/app/concepts/upload/crud.rb:7:in `<class:Upload>'
from /Users/me/code/bumble/app/concepts/upload/crud.rb:1:in `<top (required)>'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:457:in `load'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:457:in `block in load_file'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:647:in `new_constants_in'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:456:in `load_file'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:354:in `require_or_load'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:317:in `depend_on'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/activesupport-4.2.1/lib/active_support/dependencies.rb:233:in `require_dependency'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/rails/railtie.rb:9:in `block in autoload_crud_operations'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/rails/railtie.rb:4:in `glob'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/rails/railtie.rb:4:in `autoload_crud_operations'
from /Users/me/code/bumble/.gems/ruby/2.2.0/gems/trailblazer-0.2.2/lib/trailblazer/rails/railtie.rb:21:in `block (2 levels) in <class:Railtie>'
apotonick commented 9 years ago

Sounds like a Rails autoloader problem. I'm pretty sure your upload gem somewhere keeps a reference to the contract class where the validation is used in. This breaks autoloading. Can you point me to the carrierwave_direct source, I can have a quick look.

Use paperdragon if you're sick of all this ActiveRecord mess.

HuckyDucky commented 9 years ago

Ahh. That could be it. The source is at https://github.com/dwilkie/carrierwave_direct.

I'm checking out paperdragon now. It's early enough in this project that I could get away with it.

It looks like it's for images, and this particular use case is for CSV and XML files, but there doesn't appear to be anything preventing that.

One question I have as I'm almost through the TRB book as it's written thus far. I'm in the process of deprogramming myself from slamming everything into models. It's liberating, but it's taking me a while to learn where stuff goes and why it goes there. I'm trying to boil the book contents down to a series of bullet points that will help me grok Trailblazer in fullness.

Something I can't seem to get past is, under what circumstances would I use a regular method rather than create an Operation? Should I think of Operations as being a level of abstraction above a method?

It seems like most Operations are CRUD. But if I have an app where the user needs to do things like:

Do these things sound like Operations? It seems to me like they do.

Whereas, for the "Upload a file" operation, maybe the file has to be a certain size, or has to have certain headers in it. "Check header" doesn't sound like an Operation, it sounds like it's just a method. Because a user of the site doesn't care about "Checking the header", they're just trying to "Upload a file". It's the "Upload a file" Operation that needs to call a method "check_header". Am I thinking about this right?

And if I am, here's my next question. Where do I put the methods? My instinct is to make them private methods of the Operation classes.

If they don't need to be reused, that's fine. If they do need to be reused, what is the preferred way of allowing that:

  1. building some sort of PORO utility class or object that can be required and Included by whatever needs it
  2. If it's the same kind of object that is having this task performed on it, making a domain out of it so that it can be an Operation
  3. Using a Twin. I admit I don't understand Twins. I can see how they can be Compositions, but I am trying to think of a use case where I would need such a Composition outside the confines of an Operation. So Twins I still need to understand.
HuckyDucky commented 9 years ago

Nick,

I'm looking for information on Authorization, which looks like it's coming up in the next chapter of the book. Do you know any gems on Github that are doing authorization within TB? I imagine it's within the process method of the validation, but I'm probably missing other stuff.

HuckyDucky commented 9 years ago

I also wanted to offer to edit the Trailblazer book.

apotonick commented 9 years ago

Hey @HuckyDucky go ahead an email me when you have suggestions etc. for the book! Can't wait!

We will be using pundit and other gems to actually implement the roles and permissions, Trailblazer is simply gonna streamline that so you can easily have nested permissions in operations/contracts/representers.

The more feedback I get about that from people using authorization gems, the better!

HuckyDucky commented 9 years ago

I don't have your email.

apotonick commented 9 years ago

It's on my GH profile! :stuck_out_tongue:

BTW - please don't use an issue as a discussion forum. For instance, I completely missed to answer the new questions on this one.

apotonick commented 9 years ago

To answer you last questions about when to use operations: Whenever a function is a public function of your application, whether you invoke that from a controller via the UI, via the console or in a test.

Everything else does not have to be an operation, because it's not a public concept. I usually have PORO classes inline in the operation to solve "smaller" issues, as the operation is an orchestrating director and not a monolith.

Does that make it easier to understand? Feel free to email me about that, too. :stuck_out_tongue_winking_eye:

apotonick commented 9 years ago

Chapter 8 (coming in a few days) discusses a file upload, albeit with Paperdragon.

HuckyDucky commented 9 years ago

Perfect timing.