Related
I am trying to implement server side filtering for a Kendo UI grid (client only). I am not sure how to pass the filter operator and value entered on the filter box. I was able to implement server paging and want the filtering to work alongside the server paging, i.e. show page 2 of 5 items of filtered rows. I saw some example of binding the request to "DataSourceRequest" object but we do not have licence for the server side Kendo UI and have to achieve it using the client side changes only.
Here is my jQuery code:
var page = 1;
var pageSize = 5;
var title = "test";
var selectWork = function (e) {
alert("selected");
};
$("#selectWorkGrid").empty();
$("#selectWorkGrid").kendoGrid({
dataSource:
{
transport: {
read: {
url: "http://example.com/" + "work/SearchWorkJ?worktitle=" + title,
dataType: "json",
contentType: "application/json",
data: {
page: page,
pageSize: pageSize
}
},
serverFiltering: true,
parameterMap: function (data, type) {
if (type == "read") {
return {
page: data.page,
pageSize: data.pageSize
}
}
}
},
schema: {
model: {
id: "workId",
fields: {
workId: { type: "number" },
workTitle: { type: "string" },
writers: { type: "string" },
durationInMmSs: { type: "string" }
}
},
data: "data",
total: "total"
},
pageSize: pageSize,
serverPaging: true,
serverFiltering: true
},
sortable: true,
resizable: true,
columnMenu: false,
filterable: {
mode: "row",
extra: false,
operators: {
string: {
startswith: "Starts with",
eq: "Is equal to",
neq: "Is not equal to"
}
}
},
noRecords: {
template: "No results available."
},
pageable: {
numeric: false,
refresh: true,
buttonCount: 15
},
scrollable: false,
columns: [
{
field: "workTitle",
title: "Title",
template: "#=workTitle#"
},
{
field: "writers",
title: "Writers",
filterable: false,
template: "${writers == null ? '':writers}",
width: 300
},
{
field: "durationInMmSs",
title: "Duration",
filterable: false,
headerAttributes: { style: "text-align:right;" },
attributes: { style: "text-align:right;" },
width: 80
},
{ command: { text: "Select", click: selectWork }, title: "", width: 60 }
]
});
Controller action returning json:
public ContentResult SearchWorkJ(string workTitle, int page = 0, int pageSize = 0)
{
var worksJson = "";
var works = WorkService.SearchWork(workTitle, page, pageSize);
if (works != null)
{
// Set total to upto current record + 1 so that next button works in kendo
int totalCount = page * pageSize + 1;
var sortedWorks = new List<WorkViewModel>();
sortedWorks.AddRange(works.Select(w => new WorkViewModel
{
WorkId = w.WorkId,
WorkTitle = w.WorkTitle,
Writers = w.Writers,
DurationInMmSs = w.Duration
}).OrderBy(w => w.WorkTitle));
worksJson = JsonConvert.SerializeObject(new { total = totalCount, data = sortedWorks });
}
return new ContentResult { Content = worksJson, ContentType = "application/json" };
}
If you look at this
https://dojo.telerik.com/EhUNUwOr
<div id="my-grid"></div>
<script>
$('#my-grid').kendoGrid({
dataSource: {
serverFiltering: true,
serverSorting: true,
serverPaging: true,
pageSize: 5,
transport: {
read: function(options) {
$.ajax({
url: '/yourapi',
contentType: 'application/json',
dataType: 'json',
type: 'POST',
data: JSON.stringify(options.data),
success: function(result) {
options.success(result);
}
})
}
},
schema: {
id: 'Id',
data: 'Data',
total: 'Total',
errors: 'Errors',
fields: [
{ field: 'Id', type: 'number' },
{ field: 'FirstName', type: 'string' },
{ field: 'LastName', type: 'string' }
]
},
filter: {
filters: [{ field: 'FirstName', operator: 'eq', value: 'David' }]
}
},
});
</script>
This will send
{"take":5,"skip":0,"page":1,"pageSize":5,"filter":{"filters":[{"field":"FirstName","operator":"eq","value":"David"}]}}
to your server / api
now if you have a model that shares this structure you can respond in the following format
{
"Data" : <your array of models>,
"Total" : the number of models that fits your filter regardless of the filter, this helps kendo grid knowing how many pages there is for the pager.,
"Errors" : is mostely used for create and update so just return null
}
From here its a bonus to the answer above.
I noticed you are using CSharp so you have two options to apply create dynamic queries from Queryable.
use a library I open sourced
https://github.com/PoweredSoft/DynamicLinq
which is available on Nuget https://www.nuget.org/packages/PoweredSoft.DynamicLinq/
There is an example you can look at on git hub.
You'll have to adapt the code around but it should get you started.
https://github.com/PoweredSoft/DynamicLinq#how-it-can-be-used-in-a-web-api
[HttpGet][Route("FindClients")]
public IHttpActionResult FindClients(string filterField = null, string filterValue = null,
string sortProperty = "Id", int? page = null, int pageSize = 50)
{
var ctx = new MyDbContext();
var query = ctx.Clients.AsQueryable();
if (!string.IsNullOrEmpty(filterField) && !string.IsNullOrEmpty(filterValue))
query = query.Query(t => t.Contains(filterField, filterValue)).OrderBy(sortProperty);
// count.
var clientCount = query.Count();
int? pages = null;
if (page.HasValue && pageSize > 0)
{
if (clientCount == 0)
pages = 0;
else
pages = clientCount / pageSize + (clientCount % pageSize != 0 ? 1 : 0);
}
if (page.HasValue)
query = query.Skip((page.Value-1) * pageSize).Take(pageSize);
var clients = query.ToList();
return Ok(new
{
total = clientCount,
pages = pages,
data = clients
});
}
An alternative is using
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
I know there are many questions about that already on stackoverflow but none of them has been useful for me. Here is my ajax code:
function update_schedule($task){
var taskId = $task.data("taskId"),
startHour, endHour,
userId = $('#users').val();
if( !$task.hasClass('daily-task')){
startHour = getStartHour($task),
endHour = startHour + (+$task.data('time'));
}
console.log(startHour, endHour)
$.ajax({
url: '/heimdall/update_scheduled_task',
method: 'POST',
data: { "task_id": taskId, "start_hour": startHour, "end_hour": endHour, "user_id": userId },
success: function (){
console.log("SUCESS!!", data['head'])
},
error: function () {
console.log("FAILURE");
},
async: true
});
}
The controller code:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
end
I want to return the id of found or created task. I don't know how to send/receive this information. I've read about respond to but still I don't know how to use it in this case.
You may do render json: ... to return the required info to ajax in JSON format:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
render json: {scheduled_task_id: scheduled_task.id}
end
And in the ajax function's success, use it like:
success: function (data){
var data = JSON.parse(data);
console.log(data["scheduled_task_id"]);
},
First, you need to improve your controller code structure (You can rely on the default scaffold generator methods). Then, you must indicate that your method will respond to json format only, with the answer you want to return, something like this:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
if (scheduled_task && scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id]))
render json: { scheduled_task_id: scheduled_task.id }
else
render json: { error: l18n.t("error.messages.request_error") }
end
end
Then, you must modify the success and failure response methods of the jquery ajax request. Here's an example of how it might look:
$.ajax({
url: "/heimdall/update_scheduled_task",
method: "post",
data: { task_id: taskId, start_hour: startHour, end_hour: endHour, user_id: userId },
success: function(result) {
if (result["scheduled_task_id"] != null) {
console.log("Schedule record => " + result["scheduled_task_id"])
} else {
console.log("Error: " + result["error"])
}
},
error: function() {
console.log("Ajax request error");
},
// async: true => By default JQuery set true to async param.
});
Do not forget that you need to add a rule to access this method in the file config/ruotes.rb, something like this:
post update_scheduled_task, :defaults => { :format => 'json' }
I hope this helped you, regards!
jQuery's success and error callbacks rely on the status code returned by the controller. Make sure that you return the right status code when you are unable to create/read/update the object. On success, simply render a JSON with the id property set. I beleive update_attributes returns true on success:
if scheduled_task && scheduled_task.update_attributes(...)
render json: { id: scheduled_task.id }
else
head :unprocessable_entity
end
I cannot bind results from search in kendo grid. I've tried many times, I'm in trouble four days, I don't know what is wrong here,
When i debug action everything is working perfect,data is OK, return grid result are OK, but results aren't shown in kendo.
Here is my code:
<script>
$(function() {
$("a.k-button").on('click', function (e) {
debugger;
e.preventDefault();
var dataObj = serializeByFieldsWrap(".invoiceForm");
var dataUrl = $(this).data('url');
// dataObj.ToolboxId = toolboxId;
$('body').css('cursor', 'wait');
var result = $.ajax({
type: "POST",
url: dataUrl,
dataType: 'json',
data: dataObj,
//complete: $("#invoices-grid").data("kendoGrid").data.read(),
});
result.done(function (data) {
console.log(data);
var grid = $('#invoices-grid').data("kendoGrid");
grid.dataSource.data(data);
});
result.fail(function (error) {
console.log(error);
});
});
});
</script>
Controller:
public ActionResult List(DataSourceRequest command, FinanceListModel model)
{
var searchString = model.SearchJobItemNumber;
var isChecked = model.IsChecked;
var invoices = _invoiceService.GetAllInvoices(searchString, isChecked);
var gridModel = new DataSourceResult
{
Data = invoices.Select(x => {
var jobModel = x.ToModel();
return jobModel;
}),
Total = invoices.TotalCount
};
return Json(gridModel, "application/json", JsonRequestBehavior.AllowGet);
}
Kendo UI Grid:
<script>
$(function() {
$("#invoices-grid").kendoGrid({
dataSource: {
data: #Html.Raw(JsonConvert.SerializeObject(Model.Invoices)),
schema: {
model: {
fields: {
JobNumber: { type: "string" },
CustomerName: { type: "string" },
DepartmentName: { type: "string" },
DateInvoice: { type: "string" },
ValidDays: { type: "number" },
Delivery: { type: "string" },
IsPayed: { type: "boolean" },
Payed: { type: "number" },
Status: { type: "boolean" },
},
error: function(e) {
display_kendoui_grid_error(e);
// Cancel the changes
this.cancelChanges();
},
pageSize: 20,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
dataBound: function () {
var row = this.element.find('tbody tr:first');
this.select(row);
},
columns: [
#*{
field: "Status",
title: "#T("gp.Jobs.Fields.Status")",
template: '#= Status #'
},*#
{
field: "JobNumber",
title: "#T("gp.Invoice.Fields.JobNumber")",
template: '#= JobNumber #'
},
{
field: "CustomerName",
title: "#T("gp.Invoice.Fields.CustomerName")",
template: '#= CustomerName #'
},
{
field: "DepartmentName",
title: "#T("gp.Invoice.Fields.DepartmentName")",
template: '#= DepartmentName #'
},
{
field: "DateInvoice",
title: "#T("gp.Invoice.Fields.DateInvoice")",
template: '#= DateInvoice #'
},
{
field: "ValidDays",
title: "#T("gp.Invoice.Fields.ValidDays")",
template: '#= ValidDays #'
},
{
field: "Delivery",
title: "#T("gp.Invoice.Fields.Delivery")",
template: '#= Delivery #'
},
{
field: "Payed",
title: "#T("gp.Invoice.Fields.IsPayed")",
template: '#= (Payed == 2) ? "Комп." : ((Payed == 1) ? "ДЕ" : "НЕ") #'
},
{
field: "Id",
title: "#T("Common.Edit")",
width: 100,
template: '#T("Common.Edit")'
}
],
pageable: {
refresh: true,
pageSizes: [5, 10, 20, 50]
},
editable: {
confirmation: false,
mode: "popup"
},
scrollable: false,
selectable: true,
change: function(e) {
var selectedRows = this.select();
var jobId = parseInt($(selectedRows).data('job-id'));
var jobItemId = parseInt($(selectedRows).data('job-item-id'));
var result = $.get("#Url.Action("SideDetails", "Production")/" + jobItemId);
result.done(function(data) {
if (data) {
$(".job-edit .jobItemDetails").html(data);
}
});
},
rowTemplate: kendo.template($("#invoiceRowTemplate").html()),
});
});
</script>
DataSourceResult formats your server response like this:
{
Data: [ {JobNumber: "...", FieldName: "bar", ... } ],
Total: 100
}
In other words, the data items array is assigned to a Data field, and the total items count is assigned to a Total field. The Kendo UI DataSource configuration must take this into account by setting schema.data and schema.total:
http://docs.telerik.com/kendo-ui/framework/datasource/crud#schema
http://docs.telerik.com/kendo-ui/framework/datasource/crud#read-remote
schema: {
data: "Data",
total: "Total"
}
I have trailer model, and a movie model. When a movie is created, I look up all the available trailers and place each one of them in a record with the youtube link and the movie_id of the movie that's been added.
def index
trailer = Trailer.all
respond_to do |format|
format.json do
render :json => trailer.to_json(:only => [:id, :movie_id, :link])
end
end
end
And the json output,
[
{
"id":1,
"movie_id":"312221",
"link":"LsjX5dqHLuw"
},
{
"id":2,
"movie_id":"209112",
"link":"nIGtF3J5kn8"
},
{
"id":3,
"movie_id":"209112",
"link":"yViIi3gie2c"
},
{
"id":4,
"movie_id":"209112",
"link":"Onh7NbZ7F8o"
},
{
"id":5,
"movie_id":"290250",
"link":"1Vb32Kokbtg"
},
{
"id":6,
"movie_id":"290250",
"link":"1Vb32Kokbtg"
},
{
"id":7,
"movie_id":"27205",
"link":"8hP9D6kZseM"
},
{
"id":8,
"movie_id":"157336",
"link":"ePbKGoIGAXY"
},
{
"id":9,
"movie_id":"157336",
"link":"KlyknsTJk0w"
},
{
"id":10,
"movie_id":"157336",
"link":"nyc6RJEEe0U"
},
{
"id":11,
"movie_id":"157336",
"link":"Lm8p5rlrSkY"
},
{
"id":12,
"movie_id":"157336",
"link":"zSWdZVtXT7E"
}
]
Then I have my movie controller,
def index
#movie = Movie.all
respond_to do |format|
format.json do
render :json => #movie.to_json(include: :trailers)
end
end
end
The the json output,
[
{
"id":1,
"title":"Creed",
"release_date":"2016-01-21",
"image":"/xSE4NBFDzqedwa4AIj99r1Z7ljF.jpg",
"user_id":null,
"created_at":"2016-01-07T20:19:43.849Z",
"updated_at":"2016-01-07T20:19:43.849Z",
"movie_id":"312221",
"backdrop":"/nF4kmc4gDRQU4OJiJgk6sZtbJbl.jpg",
"crew":null,
"cast":null,
"trailers":[
]
},
{
"id":2,
"title":"Batman v Superman: Dawn of Justice",
"release_date":"2016-03-24",
"image":"/eJrlh2g9UGAd7R6mQAOQIIs329H.jpg",
"user_id":null,
"created_at":"2016-01-07T20:21:02.615Z",
"updated_at":"2016-01-07T20:21:02.615Z",
"movie_id":"209112",
"backdrop":"/15PbZtjRJ4zgQA8XS0otL70piQi.jpg",
"crew":null,
"cast":null,
"trailers":[
]
},
{
"id":3,
"title":"The Nice Guys",
"release_date":"2016-05-26",
"image":"/ecD35nDfjsxvDW5BtmK6YAaIkzF.jpg",
"user_id":null,
"created_at":"2016-01-07T20:22:05.960Z",
"updated_at":"2016-01-07T20:22:05.960Z",
"movie_id":"290250",
"backdrop":"/aEMBBMuK3BhKIuFu7iFSTXC41Bi.jpg",
"crew":null,
"cast":null,
"trailers":[
]
},
{
"id":4,
"title":"Inception",
"release_date":"2010-07-22",
"image":"/qmDpIHrmpJINaRKAfWQfftjCdyi.jpg",
"user_id":null,
"created_at":"2016-01-08T09:22:30.383Z",
"updated_at":"2016-01-08T09:22:30.383Z",
"movie_id":"27205",
"backdrop":"/s2bT29y0ngXxxu2IA8AOzzXTRhd.jpg",
"crew":null,
"cast":null,
"trailers":[
]
},
{
"id":5,
"title":"Interstellar",
"release_date":"2014-11-06",
"image":"/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg",
"user_id":null,
"created_at":"2016-01-08T09:22:39.120Z",
"updated_at":"2016-01-08T09:22:39.120Z",
"movie_id":"157336",
"backdrop":"/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg",
"crew":null,
"cast":null,
"trailers":[
]
}
]
Is there a way that I can look up all the trailers that have the same movie_id value as a movie and include them into the json render?
The disered result would be something like this,
{
"id":5,
"title":"Interstellar",
"release_date":"2014-11-06",
"image":"/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg",
"user_id":null,
"created_at":"2016-01-08T09:22:39.120Z",
"updated_at":"2016-01-08T09:22:39.120Z",
"movie_id":"157336",
"backdrop":"/xu9zaAevzQ5nnrsXN6JcahLnG4i.jpg",
"crew":null,
"cast":null,
"trailers":[
{
"id":8,
"movie_id":"157336",
"link":"ePbKGoIGAXY"
},
{
"id":9,
"movie_id":"157336",
"link":"KlyknsTJk0w"
},
{
"id":10,
"movie_id":"157336",
"link":"nyc6RJEEe0U"
},
{
"id":11,
"movie_id":"157336",
"link":"Lm8p5rlrSkY"
},
{
"id":12,
"movie_id":"157336",
"link":"zSWdZVtXT7E"
}
]
}
You can query for trailers by movie_id:
#movie = Movie.first
#trailers = Trailer.where(movie_id: #movie.id)
You might want to research Associations. If your Movie has_many Trailers then you can eventually just do:
#trailers = #movie.trailers
Ruby on Rails Guide: ActiveRecord Associations
You can try to show the trailers in movies
you wan to include trailers with movies in that json
def index
#movies = Movie.order('created_at desc')
respond_to do |format|
format.json do
render :json => #movies.to_json(:include => { :trailers => { :only => [:id, :movie_id, :link] } })
end
end
end
if you want to include particular trailers for movies
then in movie model
For rails > 4
has_many :trailers, -> { where(id: self.movie_id) }
I am currently working on an Ionic mobile application which will eventually take photos, attach a location and send them inside a post request to a rails endpoint. After looking at this link and this link and countless others, I have been unable to find any solid information on implementing this particular feature.
I can upload photos through the browser using a html input form, which is then added to the database and is displayed on the app via a get request.
However at the moment when taking a photo on the phone and attempting to send it via a post request directly from the app, only the location information is being received, the image is not being correctly encoded.
Here is the jSON data that has been received, its returning "image_url":"/images/main/missing.png".
{ "id":6,"city":"Greater London",
"country":"United Kingdom","created_at":"2015-05-14T21:22:22.825Z",
"updated_at":"2015-05-14T21:22:22.825Z","image_file_name":null,
"image_content_type":null,"image_file_size":null,
"image_updated_at":null,"image_url":"/images/main/missing.png" }
Here is the code:
Angular factory making post request:
.factory('Posts', function($http) {
var o = { posts: [] };
o.getAll = function() {
return $http.get('http://localhost:8100/posts').success(function(data) {
angular.copy(data, o.posts);
});
};
o.addPost = function(post) {
return $http.post('https://shielded-hamlet-4665.herokuapp.com/posts', post);
};
return o;
})
Angular Controller taking photo:
.controller("CameraCtrl", function($scope, $cordovaCamera, $http, Posts) {
var id = 0;
var options = {
quality : 75,
destinationType : Camera.DestinationType.FILE_URI,
sourceType : 1,
allowEdit : true,
encodingType: 0,
targetWidth: 380,
targetHeight: 450,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
function getLocCoords(position) {
$scope.lat = position.coords.latitude;
$scope.lon = position.coords.longitude;
$http.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + $scope.lat +',' + $scope.lon + '&sensor=true')
.success(function(data) {
var home = data.results[0].address_components;
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf("administrative_area_level_2") > -1) {
$scope.city = home[i].long_name;
break;
};
};
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf('country') > -1) {
$scope.country = home[i].long_name;
break;
};
};
})
};
$scope.takePicture = function() {
navigator.geolocation.getCurrentPosition(getLocCoords);
$cordovaCamera.getPicture(options).then(function(imageData) {
$scope.imgURI = imageData;
id ++;
var post = { id: id, country: $scope.country, city: $scope.city, image: $scope.imgURI, likes: 0, comments: [] }
Posts.addPost(post);
}, function(err) {
});
}
Post Controller from the Rails Database:
class PostsController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
#posts = Post.all
render json: #posts, :callback => params['callback'], :content_type => 'application/javascript', :methods => [:image_url]
end
def new
#post = Post.new
end
def create
Post.create(post_params)
redirect_to '/posts'
end
def post_params
params.require(:post).permit(:city, :country, :image)
end
end
I have not done a great deal of work with the ionic framework so please forgive my ignorance. Any help would be greatly appreciated.
Managed to solve this using the cordovaFileTransfer.upload method.
The rails end point was also filtering params and looking for a post object, with a image string, and only an image string was being provided.
The following code is now working
Angular factory making post request:
.factory('Posts', function($http, $cordovaFileTransfer) {
var o = { posts: [] };
o.getAll = function() {
return $http.get('https://shielded-hamlet-4665.herokuapp.com/posts').success(function(data) {
angular.copy(data, o.posts);
});
};
o.addPost = function(post) {
var options = {
fileKey: "image",
fileName: "image.jpeg",
chunkedMode: false,
mimeType: "image/jpeg",
params: { city: post.city, country: post.country, lat: post.lat, lon: post.lon }
};
$cordovaFileTransfer.upload('http://shielded-hamlet-4665.herokuapp.com/posts', post.image, options)
.then(function(result){
console.log("Code = ok");
}, function(error){
console.log("Code = " + error);
}, function(progress){});
};
return o;
})
Angular Controller taking photo:
.controller("CameraCtrl", function($scope, $cordovaCamera, $http, Posts) {
post = {};
var options = {
quality : 75,
destinationType : Camera.DestinationType.FILE_URI,
sourceType : 1,
allowEdit : true,
encodingType: 0,
targetWidth: 380,
targetHeight: 450,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
function getLocCoords(position) {
post.lat = position.coords.latitude;
post.lon = position.coords.longitude;
$http.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + post.lat +',' + post.lon + '&sensor=true')
.success(function(data) {
var home = data.results[0].address_components;
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf("administrative_area_level_2") > -1) {
post.city = home[i].long_name;
break;
};
};
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf('country') > -1) {
post.country = home[i].long_name;
break;
};
};
})
};
$scope.takePicture = function() {
navigator.geolocation.getCurrentPosition(getLocCoords);
$cordovaCamera.getPicture(options).then(function(imageData) {
post.image = imageData;
Posts.addPost(post);
}, function(err) {});
};
});
Post controller from rails database:
class PostsController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
#posts = Post.all
render json: #posts, :callback => params['callback'], :content_type => 'application/javascript', :methods => [:image_url]
end
def new
#post = Post.new
end
def create
Post.create(post_params)
redirect_to '/posts'
end
def post_params
params.permit(:city, :country, :image, :lat, :lon)
end
end