0
votes

I am trying to post these coordinates from the metadata of the image i upload with paperclip to another table called places."so coordinates go to places table." There are columns latitude and longitude in the places table. After submitting the post i run into this error. The highlighted portion of the error is self.latitude=parse_latlong(etc....).

post_id is a foreign key in the places table. This worked previously when i had latitude and longitude in the post table. but now i gave it its own table for better database structure.. i just need to know how to get my post controller to work with my places controller if that is the main problem??

Places Controller

class PlacesController < ApplicationController
    before_action :set_post

def create  
  @place = @post.places.build(place_params)


  if @place.save
    flash[:success] = "coorinates saved"
    redirect_to :back
  else
    flash[:alert] = "Check the form, something went wrong."
    render root_path
  end
end


private

def place_params  
  params.require(:place).permit(:continent, :country, :city, :address, :latitude, :longitude)
end

def set_post  
  @post = Post.find(params[:post_id])
end  
end

post controller

class PostsController < ApplicationController

before_action :authenticate_user!, :except => [:show, :index, :new]

before_action :set_post, only: [:show, :edit, :update, :destroy]

before_action :owned_post, only: [:edit, :update, :destroy]  

  def index
    @post = Post.new
    @posts = Post.all
  end

  def show

    @post = Post.find(params[:id])
  end

  def new
    @post = current_user.posts.build

    @posts = Post.all
  end

  def create
     @post = current_user.posts.build(post_params)

     if @post.save
      flash[:success] = "Your post has been created!"
      redirect_to root_path
    else
      flash[:alert] = "Your new post couldn't be created!  Please check the form."
      render :new
    end
  end

  def edit
    @post = Post.find(params[:id])
  end

  def update
     if @post.update(post_params)
      flash[:success] = "Post updated."
      redirect_to root_path
    else
      flash.now[:alert] = "Update failed.  Please check the form."
      render :edit
    end
  end

  def destroy
    @post.destroy
    flash[:success] = "Your Post has been removed."
    redirect_to root_path
  end

  private

  def post_params
    params.require(:post).permit(:image, :caption, :address)
  end

  def set_post
    @post = Post.find(params[:id])
  end

  def owned_post  
  unless current_user == @post.user
    flash[:alert] = "That post doesn't belong to you!"
    redirect_to root_path
  end
end  

end

post model

class Post < ActiveRecord::Base

belongs_to :user
belongs_to :place

has_many :comments, dependent: :destroy
has_one :place, dependent: :destroy

    validates :user_id, presence: true
    validates :image, presence: true


accepts_nested_attributes_for :place

  has_attached_file :image, styles: { :medium => "640x" }

  validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/

after_post_process :save_latlong


private

  def save_latlong
  exif_data = MiniExiftool.new(image.queued_for_write[:original].path)
  self.latitude = parse_latlong(exif_data['gpslatitude'])
  self.longitude = parse_latlong(exif_data['gpslongitude'])
end

def parse_latlong(latlong)
  return unless latlong
  match, degrees, minutes, seconds, rotation = /(\d+) deg (\d+)' (.*)" (\w)/.match(latlong).to_a
  calculate_latlong(degrees, minutes, seconds, rotation)
end

def calculate_latlong(degrees, minutes, seconds, rotation)
  calculated_latlong = degrees.to_f + minutes.to_f/60 + seconds.to_f/3600
  ['S', 'W'].include?(rotation) ? -calculated_latlong : calculated_latlong
end


end

All in All i would like to get that latitude and longitude variable updated into the database from the exif extraction.. the extraction isn't the problem but instead I believe how Im saving that information into the database is the true problem!!! thank you!!!!

2

2 Answers

1
votes

The issue seems to stem from the fact that you are updating attributes from an associated model. You could do so by calling

update_attributes(place_attributes: {latitude: , longitude: })

But I would recommend keeping this logic out of the posts model, this really is a concern for a form model where you transform the raw user inputs into a database consumable format. If you do not want to add an additional layer to your app at least move these methods to their own model. Everytime, you see a group of private method calling each other and passing state around, I think it's a good sign that they should for a class: So

class LotLangParser

  def initialize(image)
    @image = image
    @exif_data = MiniExiftool.new(image.queued_for_write[:original].path)
  end

  def lat
    parse_latlong(exif_data['gpslatitude'])
  end

  def lon
    parse_latlong(exif_data['gpslongitude'])
  end

  private

  def parse_latlong(latlong)
    return unless latlong
    match, degrees, minutes, seconds, rotation = /(\d+) deg (\d+)' (.*)" (\w)/.match(latlong).to_a
    calculate_latlong(degrees, minutes, seconds, rotation)
  end

  def calculate_latlong(degrees, minutes, seconds, rotation)
    calculated_latlong = degrees.to_f + minutes.to_f/60 + seconds.to_f/3600
    ['S', 'W'].include?(rotation) ? -calculated_latlong : calculated_latlong
  end
end

As a note I would also encapsulate MiniExiftool and inject it as a dependency in the constructor. But let's not lose sight of our goal here.

Then in your controller you can call your parser to give you the place params

def place_params
  long_lat_parser = LongLatParser(image)
  {place_attributes: {longitude:  long_lat_parser.lon,  latitude: long_lat_parser.lat}}
end

and then simply merge them into the post params:

@post = current_user.posts.build(post_params.merge(place_params))

The benenfit of this approach is that you have introduced an object with a clear responsibility and returned to AR model as mere database wrapper. In general I try to encapsulate more complex interaction in some sort of service object, but in the simple case your controller can play the role of mediator orchestrating how different object in your system interact.

0
votes

Instead of:

self.latitude = parse_latlong(exif_data['gpslatitude'])
self.longitude = parse_latlong(exif_data['gpslongitude'])

Use:

update_attributes(
  latitude: parse_latlong(exif_data['gpslatitude']),
  longitude: parse_latlong(exif_data['gpslongitude'])
)