I am writing an web application which runs on iPads in taxis. Now i want every device to call the server every X minutes and say "i am still alive".
For that reason i have a 'latest_activity' field which i would like to update with rails' Time.now
What's the best way to do this? I was thinking about sending a GET to "/app?update_id=52" and then rails would look for params[:update_id] and update the field but this feels so dirty and i think there has to be a better way to do this.
Updated with solution below
I would PUT to the update action in the controller for whatever has the latest_activity field. So say the field is on Client, I would use RESTful style routes like so:
# routes.rb
resources :clients
And then use the endpoint:
PUT /clients/52
And of course then ClientsController gets an update method which sets the field to Time.now as you already mentioned.
Backbone supports this type of url scheme natively/easily. You have a Client model on the frontend, you simply do:
someClient.set( { latest_activity: new Date() } );
someClient.save();
Although that sort of says to me that you might just let the frontend generate the timestamp rather than doing it in rails.. otherwise you are really just sending a fake value (either a real date that wont be used or just, say, 1 and then 'notice' that on the backend). If you send a real timestamp you can just do:
# app/controllers/clients_controller.rb
def update
c = Client.find( params[:id] )
c.latest_activity = params[:latest_activity]
c.save
render :json => { :ok => true }
end
Or something along those lines.
Solution
I've taken a slightly different path. Now i've added a ping method to my devices_controller
def ping
if params[:id]
#device = Device.find(params[:id])
#device.latest_activity = Time.now
#device.save()
end
respond_with(#device)
end
And added a member to the Devices resource like so
resources :devices do
# Device Ping ('/api/v1/devices/#{id}/ping')
put 'ping', :on => :member
end
Now i am just sending a PUT request to /api/v1/devices/ID/ping, from Backbone.js in my case, like so
ping: ->
if localStorage.device_id?
setInterval =>
$.ajax
type: "PUT"
url: "/api/v1/devices/#{localStorage.device_id}/ping"
, 1000 * 60 * 15 # every 15 Minutes
Related
I have used Ruby on Rails to create a simple Rest api server. I have added to my route.rb this, which sends the whole path to myapp's controller's update method, which I need:
put 'dreceiver/*other', to: 'myapps#update'
My controller:
class MyAppsController < ApplicationController
protect_from_forgery with: :null_session
# PUT
# Expected Parameters: {"other"=>"api/1.0/file/abc123"}
def update
#Need to grab last part of path sent in:
if params[:other] =~ /api\/1.0\/file\/.*/
batchid = params[:other].split('/').last
else
batchid = nil
end
unless batchid.nil?
render :text => '', :status => 201
else
render :text => '', :status => 401
end
return
end
end
My problem: I expect a gzip file to be sent in the body of the PUT request. I need to save it to the file system. I see lot's of examples of storing it in the db using paperclip, but I really don't want to do any migrations or deal with the db if I don't have to... this is just to test some client code sending the file. Thanks for your help in advance.
Paperclip does not store uploaded files in the database, it stores them on the filesystem and then writes the filename to the database (usually -- it can be configured in multiple ways).
Multipart POSTs (and PUTs or PATCHes) work just like regular POSTs. Rails will automatically create a Hash-like object from the request body. One of the values in this hash should be a object that responds to #tempfile that you can treat pretty much just like a normal File object.
def update
gzip_file = params[:uploaded_file].tempfile # replace uploaded_file with the field name
...
end
Is it possible for the route below to dynamically select different controllers or at least for a single controller to dynamically call another controller?
get '*path' => 'routing#show
For example:
/name-of-a-person => persons#show
/name-of-a-place => places#show
I recall reading something about Rails 5 that would enable this but I can't find it again to save my life. It's possible I imagined it.
Another options is to have a RoutingController that depending on which path is received will call different controllers.
The use case is I have URLs in the database with a type, and the controller depends on what type is the URL. I'm thinking something like this:
get '*path' do |params|
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
'persons#show'
when 'place'
'places#show'
end
end
I post my second best solution so far; still waiting to see if anyone knows how to do this efficiently within the routes.
class RoutingController < ApplicationController
def show
url = Url.find_by!(path: params[:path])
url.controller_class.dispatch('show', request, response)
end
end
Hat tip to André for the idea.
You could define one controller and inside its action make something like this:
def generic_show
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
controller = PersonController.new
controller.request = request
controller.response = response
controller.show
when 'place'
...
end
end
However, I would recommend you to move the code you want to reuse to other classes and use them in both controllers. It should be easier to understand and maintain.
I think you may be able to do it using advanced routing constraints.
From: http://guides.rubyonrails.org/routing.html#advanced-constraints
If you have a more advanced constraint, you can provide an object that responds to matches? that Rails should use. Let's say you wanted to route all users on a blacklist to the BlacklistController. You could do:
class BlacklistConstraint
def initialize
#ips = Blacklist.retrieve_ips
end
def matches?(request)
#ips.include?(request.remote_ip)
end
end
Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: BlacklistConstraint.new
end
I don't think the Rails guide example is particularly good, because this problem could essentially be solved in your application controllers before_action.
In this example, the constraint is used for IP filtering, but you could also implement matches? to check if it's a person. I would imagine something like
def matches?(request)
Person.where(slug: request.params[:path]).any?
end
And as such, the Rails router can decide whether or not to dispatch the request to the persons#show action.
I am facing an issue with accessing a particular variable of a method say A , in another method say B in the controller.. The size of the object(variable) is too big since it contains the results of a service call made.. My usecase is like on selecting an option from a drop down box, it redirects to a method B in controller and the same object(variable) should be parsed. How can I access the variable in the other method?
I tried storing in a cookie and since the size is too big I am getting Cookie Overflow exception. I am not using a DB. So I guess using memcache won't work. Also tried storing it as hidden field in view and passed its value as a data through ajax call. But I am getting it as a string. Tried to specify datatype as json and several other ways.. but of no use..Using ##var also din work..Not sure why..
Code:
On change of the drop down:
$(document).ready(function(){
$('#filter_service').change(function() {
$.ajax({type: "GET",
url: "/device_troubleshootings/query_operation",
data: { filter_service: $('# filter_service').val()},
});
});
});
Service call:
def log_results
//Service call
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
Here, I am trying to access #dsn_result object in "/device_troubleshootings/query_operation” method.
Please suggest me ways to access the variable.
MVC
I think you're getting confused with how Rails should work
Remember, Rails (which is just a framework for Ruby) is built on the "MVC" programming pattern. This means each time you send a request to your Rails application, it has to be handled by a single controller#action which you will then allow you to pull the relevant data from your models
The problem you have is you're trying to load multiple controller methods, and pass the same data to both. This might work in Ruby, but not Rails (Rails is stateless):
--
Model
The correct way to handle this type of setup is by creating another request for your application, which will load another controller#action, allowing you to access the data you need
As demonstrated by the MVC diagram above, each time you send a request to Rails, it's basically a new request. This means that unless you've persisted your data in the likes of a cookie, you'll need to load the data from the model.
The problem you have is you're trying to store an entire data-set in the front-end of your system. This issue is very bad, as not only is it inefficient, but it goes against the MVC pattern completely.
You'll be much better storing the bare-minimum data set you need in the front-end (ids or similar), which you will then be able send to your controller via ajax; building a new data-set from
--
Class Variables
You mentioned you tried to declare some ##class variables to no avail. The problem with this is that the class vars will only be available for an instance of a class.
As mentioned, since Rails is stateless, the class variables won't persist between requests (how can they?). I think you know this already, considering you've been trying to use cookies to store your data
The way to resolve this is to rebuild the data each time from the model (as detailed above)
Solution
The solution for you is to "go stateless"
Here's how:
Treat Method A and Method B as completely separate "ACTIONS"
When using these actions, you need to consider the smallest piece of data to pass between the two
To load Method B, you need to send a new request from your browser (as if you've never loaded Method A before)
Your method_a can be handled in the "standard" way:
#config/routes.rb
resources :your_controller do
collection do
get :method_a
get :method_b
end
end
This will mean that you can load method_a relatively simply:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def method_a
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
As you know, the #dsn_result will not persist through to the next request.
There are two ways to resolve this (set a CONSTANT -- if you're pulling from an API, this will give you a single call -- or use a before_action to set the variable for as many actions as you need). I'll detail both for you:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :set_log_data
def method_a
end
def method_b
end
private
def set_log_data
#get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
#dsn_result = #get_log_results.logs_result_obj_list
end
end
This will work if you pull data from your own data-set (using the models), however, the better way to do this in your case will likely be to set a constant (considering, of course, that you don't want the data to change):
#config/initializers/dsn_result.rb
get_log_results = LogQueryService.client.get_logs(Com::Amazon::Logqueryservice::DeviceSerialNumberQuery.new(:search_text => # search , :index => 'dms', :index_type => '_all', :from_time_stamp => #from_time_stamp, :to_time_stamp => #to_time_stamp))
DSN_RESULT = get_log_results.logs_result_obj_list
In my case I solved with global variable $my_global_var
So my files look like this
routes.rb
Rails.application.routes.draw do
resources :pages
root 'pages#index'
post 'pages/test'
end
pages_controller.rb
class PagesController < ApplicationController
def firstaction
$my_global_var = "My global var"
puts $my_global_var
end
def secondaction
puts $my_global_var
end
end
index.html.erb
<%= button_to 'Test', pages_test_path, method: :post %>
I've run into a weird problem and after a bunch of research can't get any closer. I've got several forms that upload files via Carrierwave. When I upload the information, part of the route gets cut off (I think).
For example, I have a multi-part form submitting to:
https:/domain/programs/223/add_file as POST
but on submission I get the error
No route matches [POST] "/223/add_file"
even though what's in my address bar is the complete route. And if submit the complete route as a GET request it works fine. When I run rake routes the route shows up just fine.
Here is a subset of my route:
resources :programs do
match "add_file" => "programs#add_file"
If it matters, I'm running Rails 3.2.2 with Passenger on Apache. The problem only happens on this production server, never in development.
Any ideas? I'm stuck on this one as it effects multiple routes and I've tried defining a custom route just for that form with no luck.
Update:
When I remove multi-part => true or the file_field_tag from the form it fixes the problem. It's still an issue but seems to be less about routing than about the form with file uploads.
Create passenger_extension.rb in the lib folder with this code:
Passenger 3
module PhusionPassenger
module Utils
protected
NULL = "\0".freeze
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
end
end
Passenger 5
module PhusionPassenger
module Utils
# Utility functions that can potentially be accelerated by native_support functions.
module NativeSupportUtils
extend self
NULL = "\0".freeze
class ProcessTimes < Struct.new(:utime, :stime)
end
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
def process_times
times = Process.times
return ProcessTimes.new((times.utime * 1_000_000).to_i,
(times.stime * 1_000_000).to_i)
end
end
end # module Utils
end # module PhusionPassenger
And then in 'config/application.rb' do:
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib)
require 'passenger_extension'
end
And then restart a webserver.
NOTICE: I'm not sure that this doesn't break any other functionality so use it on your own risk and please let me know if you find any harm from this approach.
One issue here is you're not specifying whether the route is defined on the collection or a member. Which one of these is the correct route?
programs/:id/add_file
programs/add_file
You should construct your routes like this:
resources :programs do
post 'add_file', :on => :member
end
or
resources :programs do
member do
post 'add_file'
end
end
The above will take post requests at programs/:id/add_file and send them to ProgramsController.add_file with the params[:id] as the program id.
If you want this on the collection, you could do:
resources :programs do
post 'add_file', :on => :collection
end
or
resources :programs do
collection do
post 'add_file'
end
end
This would take post requests at programs/add_file and send them to ProgramsController.add_file, but no params[:id] would be set.
In general you should always specify whether routes are on the collection or member, and you should specify which verb a route should accept (ie use 'get' or 'post' etc. instead of 'match').
Try the above and see if that solves your problem, if not please let me know and I'll take another look.
I think you may need to add
:via => [:post]
to your route specification. It seems odd that it'd work on development and not on production, but as I understand rails routing, the matcher that you've added is only going to respond to get.
Try changing your match to
match "add_file" => "programs#add_file", :via => [:post]
Also, based on the answer just submitted by Andrew, you're probably better off using the member specifier to be explicit about the fact that the operation is happening on a particular Program with a particular id, and not the collection. It also should save some code in your add_file method which is probably working hard to get the id parameter from the url.
There are several stages to this, and as I am relatively new to rails I am unsure if I am approaching this in the best way.
Users follow Firms, Firms applications open and close on certain days. If a user follows a firm I would like them to automatically get an email when a) the firms application opens, b) a week before the firms applications close, c) on the day that the firms applications close.
I have tried using named scope. I have the following model method (I presume this will need a little work) setting each firms scope, depending on the date.
model firms.rb
def application_status
if open_date == Today.date
self.opening = true
else
self.opening = false
end
if ((close_day - Today.date) == 7)
self.warning = true
else
self.warning = false
end
if close_day == Today.date
self.closing = true
else
self.closing = false
end
end
I would like this method to be called on each firm once a day, so that each firm has the appropriate scope - so I have tried using the whenever gem (cron) and the following code. Running the above model method on each firm.
Schedule.rb
every 1.day do
runner "Firm.all.each do |firm|
firm.application_status
end"
end
Then for each of the scopes opening, warning, closing i have a method in the whenever schedules file, For simplicity I shall show just the opening methods. The following queries for all firms that have had the opening scope applied to them, and runs the application_open_notification method on them.
Schedule.rb
every 1.day do
runner "Firm.opening.each do |firm|
firm.application_open_notification
end"
end
This calls the following method in the Firm.rb model
def application_open_notification
self.users.each do |user|
FirmMailer.application_open(user, self).deliver
end
end
Which in turn calls the final piece of the puzzle... which should send the user an email, including the name of the firm.
def application_open(user,firm)
#firm = firm
#user = user
mail to: #user.email, subject: #firm' is now accepting applications'
end
end
Is this a viable way to approach this problem? In particular I am not very familiar with coding in the model.
Many thanks for any help that you can offer.
I'll guess that opening, warning and closing are database fields, and you have scopes like:
class Firm < ActiveRecord::Base
scope :opening, :where => { :opening => true }
# etc
end
There is a general rule for database (and, well, all storage): don't store things you can caculate, if you don't have to.
Since an application's status can be dermined from the day's date and the open_date and close_day fields, you could calculate them as needed instead of creating extra fields for them. You can do this with SQL and Active Record:
scope :opening, :where { :open_date => (Date.today .. Date.today+1) }
scope :warning, :where { :close_day => (Date.today+7 .. Date.today+8) }
scope :closing, :where { :close_day => (Date.today .. Date.today+1) }
(Note that these select time ranges. They may have to be changed depending on if you are using date or time fields.)
But there is another issue: what happens if, for some reason (computer crash, code bug etc) your scheduled program doesn't run on a particular day? You need a way of making sure notices are sent eventually even if something breaks. There are two solutions:
Write your schedule program to optionally accept a date besides today (via ARGV)
keep flags for each firm for whether each kind of notice has been sent. These will have to be stored in the databse.
Note that scopes aren't necessary. You are able to do this:
Firm.where(:open_date => (Date.today .. Date.today+1)).each do |firm|
#...
end
but the scope at least encapsulates the details of identifying the various sets of records.