Ruby on Rails Best practices - Big Controller vs S

2020-01-27 07:00发布

问题:

I need some informations for the best practices in Ruby on Rails, especially with Controller who have to do a lot of things, so, a simple "show" action is now up to lines. I know, it's not really good, and I have specific code in it.

Here is a sample code:

def show
    sound = Sound.find(params[:id])
    @xml_path = File.dirname(sound.file.path)
    s3 = AWS::S3.new(
        :access_key_id => 'XXX',
        :secret_access_key => 'XXX')
    @url = s3.buckets['dev'].objects[sound.file.path[1..-1]].url_for(:read, :expires => 10*60)

    if sound.id_job != 0 && sound.transcript_progress != 100
      @response = Savon.client("http://srap.php?wsdl").request(:avance) do
        soap.body = { 
         :Jeton => "abcdef",
         :ID_job => sound.id_job,
        }
      end
      @response = @response.to_hash
      @progress = @response[:avance][:avancement].to_s.split("#")[1]# ID_job received is formed like "OK#123", we keep "123"
      if @progress == "Termine"
         sound.transcript_progress = 100
      elsif @progress == "ERROR"
        flash.now[:alert] = "Oups, il semblerait que le fichier soit illisible, ou qu'il n'y ait rien a ecouter !"
      elsif @progress != "Queued"
        sound.transcript_progress  = @response[:avance_response][:avancement].to_s.split("#")[2].split("%")[0].to_i
      end
      sound.save
    end

    if sound.transcript_progress == 100 # If transcription finished
      # Get XML File URL on the FTP
      @xml_path = Savon.client("http://srap.php?wsdl").request(:donneResultat) do
      soap.body = { 
       :Jeton => "XXX",
       :FichierSon => sound.id_job
      }
      end

      # Parse XML Path URL on Kimsufi
      @xml_path = @xml_path.to_hash[:donne_resultat_transposition_response][:chemin_fichier].to_s.split("#")[2].to_s.split("/")[5]


      # Create local directory (/tmp/sounds) for XML Temp Save
      if ! File.directory?(Rails.root.to_s + '/tmp/sounds')
        Dir.mkdir(Rails.root.to_s + '/tmp/sounds')
      end
      # Get XML from FTP
      ftp=Net::FTP.new                                     
      ftp.connect("ftp.com", 21)                                                         
      ftp.login("XXX", "XXX")                
      if ftp.closed?
        flash.now[:alert] = "Oups, il semblerait qu'il y ait eu un problème ! Merci d'actualiser la page"
      else  
        ftp.passive = true
        ftp.chdir('results')
        ftp.getbinaryfile(@xml_path, Rails.root.to_s + '/tmp/sounds/' + @xml_path)
        ftp.close
      end

      # Send XML on S3
      s3 = AWS::S3.new(
        :access_key_id => 'XXX',
        :secret_access_key => 'XXX')
      @xml_new = (File.dirname(@sound.file.path) + '/' + File.basename(@xml_path))[1..-1]
      s3.buckets['dev'].objects[@xml_new].write(Pathname.new(Rails.root.to_s + '/tmp/sounds/' + @xml_path))
      @file = s3.buckets['dev'].objects[@xml_new].read()
    end


    # A lot of logic again, i've not did it yet

  end

As you can see, I have a lot of logic here, I have to check if the transcription is over, if not, update the progress_bar (@sound.transcript_progress), if yes, i first have to connect to a soap action to get the XML path, then get the XML via FTP, then stock it to the Amazon S3 (Shitty SOAP, i have to reparse all response...).

In all my action controller, i have to connect on S3 / SOAP / FTP, not in the same order.. So i i'm thinking to do a class for each, like in C++, an abstraction. I want the stuff done, i don't care (a lot) how it's done. But what's the best practice with the MVC? I have to make a new folder "Class?" A new controller?

回答1:

This is more of a long-form comment, since it explains the origin of your dilemma, but provides no solutions.

The problem actually is caused by misinterpretation of MVC, which RoR popularizes.

It is the combination of two factors, which are causing this implosion of controller:

  • On the one side you have anemic model, because, instead of real model layer, RoR uses collection of ORM instances. The reason for it is that Rails was originally created to be a framework for fast prototyping (generation of throw-away code). And prototyping is exactly what active record is best at. Using scaffolding, you can easily generate active record structures from existing databases.

    But this cause some of the domain business logic to leak in your controllers.

  • On the other side you have the non-existent view. Since the goal was prototyping, Rails favored getting rid of views, which can actually contain presentation logic, by merging them into the controllers. The, now missing, views were replace with simple templates, which are just called "views".

    This forces controllers to contain presentation logic.

These two factors would be the reason, why I am tempted to assert, that RoR is not even MVC framework. The resulting pattern is actually closer to Model-View-Presenter. Though it has been simplified to the point at which it starts to break Separation of Concerns.



回答2:

Most of your logic does not belong in the controller. The controllers responsibility is to tie input (HTTP requests and their parameters) to output (your views). Everything else is business logic that should be implemented in the model - Sound in your case, it looks like. Each of your if blocks, for example, would be a good candidate to implement as an instance method of the Sound class. If you find yourself reusing code (like the AWS storage bit) across various models, implement them in a Module (under lib) and include that module in those models.



回答3:

It looks like all of that should be refactored into a model (or library module) and broken into smaller functions. The best reason for this is because then you can set up unit tests to test the smaller parts individually. The controller simply needs to instantiate the model and return the data to the browser.