Rails 4 Angularjs Paperclip jak wgrać plik

Jestem początkujący w manipulowaniu angularjs za pomocą Rails 4, które dostarczają tylko api. staram się stworzyć prostą usługę AngularJS do przesłania pliku. Ale używam Paperclip do zarządzania plikami i mam pewne problemy.

Po pierwsze, nie rozumiem, jak prawidłowo zebrać plik wejściowy. Widziałem wiele pluginów lub dyrektywy fat, aby to zrobić. Ale chcę tylko prostą dyrektywę, która zbierze moje akta i umieści w moim modelu ng.

I wreszcie chcę wiedzieć, czy jest to bardziej efektywne zakodować mój plik w Base64 ?

Mój Kontroler Rails

class Api::EmployeesController < Api::BaseController
  def create
    employee = Employee.create(employee_params)
    if employee.save
      render json: employee
    else
     render :json => { :errors => employee.errors.full_messages }, :status => 406
     end
  end

  def employee_params
    params.require(:employee).permit(:first_name,:mobile_phone,:file)
  end
end

Mój Serwis Angularjs

angular.module('test').factory 'Employee', ($resource, $http) ->
 class Employee
  constructor: (errorHandler) ->
  @service = $resource('/api/employees/:id',
  {id: '@id'},
  {update: {method: 'PATCH'}})
  @errorHandler = errorHandler

  create: (attrs, $scope) ->
    new @service(employee: attrs).$save ((employee) ->
      $scope.employees.push(employee)
      $scope.success = true
      $timeout (->
        $scope.success = false
      ), 3000
    ), @errorHandler

Mój Kontroler Angularjs

angular.module('test').controller "EmployeesController", ($scope, $timeout,  $routeParams, $location, Employee) ->

$scope.init = ->
 @employeeService = new Employee(serverErrorHandler)
 $scope.employees = @employeeService.all($scope)

$scope.createEmployee = (employee) ->
  if $scope.employeeFirstName
    @employeeService.create (
      first_name: $scope.employeeFirstName
      last_name:     $scope.employeeLastName
      promotion: $scope.employeePromotion
      mobile_phone: $scope.employeeMobilePhone
      nationality: $scope.employeeNationality
      social_number: $scope.employeeSocialNumber
      born_place: $scope.employeeBornPlace
      employee_convention: $scope.employeeConvention
      employee_type: $scope.employeeType
  ), $scope
  else
    $scope.error = "fields missing"
Author: Alter Lagos, 2013-12-11

2 answers

Po kilku dniach rozwiązywania problemów i zastanawiania się, jak działają obie technologie (jestem nowy w obu -.- ), Udało mi się coś zrobić. Nie wiem, czy to najlepszy sposób, ale to działa. Jeśli ktoś ma jakieś ulepszenia, chętnie je usłyszę.

Ogólnie zrobiłem co następuje:

  • Tworzenie dyrektywy w AngularJS do obsługi przesyłania plików
    • zakodował plik jako łańcuch base64 i podłączył go do obiektu JSON.
  • Rails kontroler zdekodował Łańcuch base64 za pomocą StringIO i ponownie podłączył plik do parametrów
    • następnie zaktualizowałem lub stworzyłem model z nowymi zaktualizowanymi parametrami.

To było naprawdę okrężne, więc jeśli jest inny sposób, aby to zrobić, chciałbym wiedzieć!

Używam Rails 4 i najnowszej stabilnej wersji AngularJS, Paperclip i Restangular.

Oto powiązany kod:

Angularjs Dyrektywa

var baseUrl = 'http localhost:port'; // fill in as needed

angular.module('uploadFile', ['Restangular']) // using restangular is optional

.directive('uploadImage', function () {
return {
 restrict: 'A',
 link: function (scope, elem, attrs) {
  var reader = new FileReader();
  reader.onload = function (e) {
    // retrieves the image data from the reader.readAsBinaryString method and stores as data
    // calls the uploadImage method, which does a post or put request to server
    scope.user.imageData = btoa(e.target.result);
    scope.uploadImage(scope.user.imagePath);
    // updates scope
    scope.$apply();
  };

  // listens on change event
  elem.on('change', function() {
    console.log('entered change function');
    var file = elem[0].files[0];
    // gathers file data (filename and type) to send in json
    scope.user.imageContent = file.type;
    scope.user.imagePath = file.name;
    // updates scope; not sure if this is needed here, I can not remember with the testing I did...and I do not quite understand the apply method that well, as I have read limited documentation on it.
    scope.$apply();
    // converts file to binary string
    reader.readAsBinaryString(file);
  });
 },
 // not sure where the restangular dependency is needed. This is in my code from troubleshooting scope issues before, it may not be needed in all locations. will have to reevaluate when I have time to clean up code.
 // Restangular is a nice module for handling REST transactions in angular. It is certainly optional, but it was used in my project.
 controller: ['$scope', 'Restangular', function($scope, Restangular){
  $scope.uploadImage = function (path) {
   // if updating user
    if ($scope.user.id) {
      // do put request
      $scope.user.put().then( function (result) {
        // create image link (rails returns the url location of the file; depending on your application config, you may not need baseurl)
        $scope.userImageLink = baseUrl + result.image_url;
      }, function (error) {
        console.log('errors', JSON.stringify(errors));
      });
    } else {
      // if user does not exist, create user with image
      Restangular.all('users')
      .post({user: $scope.user})
      .then(function (response) { 
        console.log('Success!!!');
      }, function(error) {
        console.log('errors', JSON.stringify(errors));
      });
    }
   };
 }]
};
});

Plik kątowy z dyrektywą

<input type="file" id="fileUpload" ng-show="false" upload-image />
<img ng-src="{{userImageLink}}" ng-click="openFileWindow()" ng-class="{ hidden: !userImageLink}" >
<div class="drop-box" ng-click="openFileWindow()" ng-class=" {hidden: userImageLink}">
    Click to add an image.
</div>

Tworzy to ukryte wejście pliku. userImageLink jest ustawiany w kontrolerze, podobnie jak metoda openFileWindow(). Jeśli obraz użytkownika istnieje, wyświetla się, w przeciwnym razie wyświetla pusty div z informacją o kliknięciu, aby przesłać obraz.

W kontrolerze odpowiedzialnym za powyższy kod html mam następującą metodę:

// triggers click event for input file, causing the file selection window to open
$scope.openFileWindow = function () {
  angular.element( document.querySelector( '#fileUpload' ) ).trigger('click');
  console.log('triggering click');
};

Strona Rails

W kontrolerze modelu użytkownika, Mam następujące metody:

# set user params 
before_action :user_params, only: [:show, :create, :update, :destroy]

def create
  # if there is an image, process image before save
  if params[:imageData]
    decode_image
  end

  @user = User.new(@up)

  if @user.save
    render json: @user
  else
    render json: @user.errors, status: :unprocessable_entity
    Rails.logger.info @user.errors
  end
end

def update
  # if there is an image, process image before save
  if params[:imageData]
    decode_image
  end

  if @user.update(@up)
    render json: @user
  else
    render json: @user.errors, status: :unprocessable_entity
  end
end

private 

  def user_params
    @up = params.permit(:userIcon, :whateverElseIsPermittedForYourModel)
  end

  def decode_image
    # decode base64 string
    Rails.logger.info 'decoding now'
    decoded_data = Base64.decode64(params[:imageData]) # json parameter set in directive scope
    # create 'file' understandable by Paperclip
    data = StringIO.new(decoded_data)
    data.class_eval do
      attr_accessor :content_type, :original_filename
    end

    # set file properties
    data.content_type = params[:imageContent] # json parameter set in directive scope
    data.original_filename = params[:imagePath] # json parameter set in directive scope

    # update hash, I had to set @up to persist the hash so I can pass it for saving
    # since set_params returns a new hash everytime it is called (and must be used to explicitly list which params are allowed otherwise it throws an exception)
    @up[:userIcon] = data # user Icon is the model attribute that i defined as an attachment using paperclip generator
  end

Użytkownik.plik rb miałby taki:

### image validation functions
has_attached_file :userIcon, styles: {thumb: "100x100#"}
#validates :userIcon, :attachment_presence => true
validates_attachment :userIcon, :content_type => { :content_type => ["image/jpg", "image/gif", "image/png"] }
validates_attachment_file_name :userIcon, :matches => [/png\Z/, /jpe?g\Z/]
Myślę, że to wszystko, co jest istotne. Mam nadzieję, że to pomoże. Prawdopodobnie opublikuję to gdzieś indziej trochę wyraźniej, gdy będę miał czas.
 13
Author: rcheuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 14:52:56

Ale chcę tylko prostą dyrektywę, która zbierze mój plik i włoży mój ng-model

Ng-file-upload po prostu to robi i jest lekkim, łatwym w użyciu rozwiązaniem między przeglądarkami, które obsługuje postęp/przerywanie, przeciąganie i upuszczanie oraz podgląd.

<div ng-controller="MyCtrl">
   <input type="file" ngf-select ng-model="files" multiple>
</div>

$scope.$watch('files', function(files) {
  for (var i = 0; i < $files.length; i++) {
      var file = $files[i];
      $scope.upload = $upload.upload({
          url: 'server/upload/url', 
          file: file,
      }).progress(function(evt) {
         console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
      }).success(function(data, status, headers, config) {
         console.log(data);
      });
   }
});
 4
Author: danial,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 21:33:30