How to call function when post request succeed in Modelviewset - post

# views.py
import schedule_update.send_the_result
class UpdatedTypeViewSet(viewsets.ModelViewSet):
queryset = Updated_Type.objects.all()
serializer_class = UpdatedTypeSerializer
# Is it correct to code here, if I would like to call function when post request succeed, and how?
# send_to_redmine.py
def Send_The_Result():
# get the data from request and send it to redmine.
if __name__ == '__main__':
Send_The_Result()
Here's my source code and there're 2 questions that I stuck in Modelviewset.
Is it possible to call Send_The_Result when post request succeed in Modelviewset?
When the post request sent, how do I bring the data into Send_The_Result?
Please let me know, if any further information is needed, thank you.

Django Rest Framework being an abstraction over Django, implements Web methods through actions which is little different from the basic HTTP protocols. For example: POST is implemented through create method.
Equivalent ModelViewSet method:
def create(self, *args, **kwargs):
return super().create(*args, **kwargs)
The create method in ModelViewSet calls the serializer, evaluates request, and if valid, stores in database through Model and returns the response.
So, this gives you three levels of space, where you want to execute your some_function_on_successful_task_completion.
You could override in view's create method,
You can override in Serializer's create method.
You can override in Model's save method.
For example, in your Model:
def save(self, *args, **kwargs):
# normal stuff, save to db.
# easier here, as you have every information you need to perform operations
return obj

Related

Get Controller Object and Controller Strong Params in Application Controller

Hello people. I'm creating a log process in my Rails 5 application, inside the application controller. I'm creating there because I want to call the log process inside many controllers with a before_save property. The log will save the changes that user performs in the form on edit view template. The problem is that I can't get the <ObjectController:> inside application controller. I've already got the instance variable from the controller, but I need the ObjectController too, because I have to get the strong parameters from controller object. The strong parameters holds all data that user inserted on input fields.
This is what I've done already:
app/controllers/application controller
def log
#controlr = instance_variable_get("##{controller_name.singularize}") #get the edited object
attribs = #controlr.attribute_names #get object table column names
edited_data = controlr_params #stuck here!
ctrlr = #controlr.attributes #retrive object data from db
...
##compare the edited_data with the actual data from db and check if something was changed
end
So, I need to obtain the Controller Object to access the strong parameters in order to compare if user edited any data. I'm not sure if this is the best way/practice to do this. If there is a better way, I'd like to know. But I need to call this process in a great number of controllers that require a data log.
Thanks for you time and sorry any bad english..
If params method won't help you to achieve your goal (but it's worth to try) you can always access current instance of controller object by calling self in context of any instance method or action.
To test you can put byebug in any action, call that action in browser with additional parameters and type self in console.
For example, in controller:
class UsersController < ApplicationController
def show
byebug
end
end
in browser:
localhost:3000/?some_param=1234&another_param=testing
There will be a lot of useful stuff in there, like self.instance_variables => [.... :#_request, ... :#_params].
Also request method contain all info about current request including parameters.
Hope that'll help.

Share a Net::IMAP connection between controller actions

I have only one controller and some actions in it to handle different functionalities related to IMAP. So my problem is I don't want to create a separate connection for every action. For example in an action I can do something like(it is not the actual code):
def index
#imap = Net::IMAP.new(server, 993, true)
#imap.login(user, password)
#imap.select("INBOX")
end
Again in another action inside the same controller, if I need to do something related to IMAP then I will have to create the #imap variable again.
I am working with IMAP first time so as per my understanding new method in each action will create another connection to the server and I have heard google has connection limit (15) for the number of IMAP connections.
I can not serialize this connection object or store it in any other service like Redis or Memcached or cache it, So how can I create this connection once and use it all other actions, at least actions inside the same controller if possible? If not possible then any other solutions to handle this problem?
And of course I can cache the data I need from the mailbox but that can't help much since there are some other actions which won't need the data, it will need to do so some operations in the mailbox like deleting mails, so that will need the connection instance.
How about you create a service object (singleton) that wraps you Net::IMAP. You can stick it in app/services/imap_service.rb or something like that. For an example on what that would look like:
require 'singleton' # This is part of the standard library
require 'connection_pool' # https://github.com/mperham/connection_pool
class IMAPService
include Singleton
def initialize
#imap = ConnectionPool.new(size: 15) { Net::IMAP.new(server, 993, true) }
end
def inbox(user, password)
#imap.with do |conn|
conn.login(user, password)
conn.select("INBOX")
end
end
end
You access this singleton like IMAPService.instance e.g. IMAPService.instance.inbox(user, password). I added in the connect_pool gem as per our discussion to make sure this is thread safe. There is no attr_reader :imap on IMAPService. However, you can add one so that you can directly access the connection pool in your code if you don't want to include all of the necessary methods here (although I recommend using the service object if possible). Then you can do IMAPService.instance.imap.with { |conn| conn.login(user, password) } and don't need to rely on methods in IMAPService.
It's worth noting that you don't have to use the Singleton mixin. There is a really good article on Implementing "the lovely" Singleton which will show you both ways to do it.
If you want the connection to stay open between requests you can not store it as an instance variable in your controller since each request will have its own instance of the controller.
One way to store the connection is to use a singleton.
Here is an example:
class ImapService
attr_accessor :imap
def initialize
#imap = Net::IMAP.new("imap.gmail.com", 993, true)
#imap.login("username#gmail.com", "password")
#imap.select("INBOX")
end
##instance = ImapService.new
private_class_method :new
def self.instance
return ##instance
end
end
This will open the connection the first time you access it, and if you access it again, it will use the old connection.
You would access the imap variable with ImapService.instance.imap anywhere in your application.

how to create an action controller helper class and how to access that in controller action?

Hi all a newbie question.
I am creating one rails application where after showing result to user i need to perform some other operations in background to update database.
(sorry i am poor at explaining things, see my example code for situation)
In my controller's action i am using third-party api to fetch data from remote server.
After showing fetched data to user i want to update database table with customized data on fetched data.
class MyController < ApplicationController
def SomeAction
#some logic
#result = FetchDataFromApi#Using third party api to fetch huge data
#show result to user
#after showing i need to do following operations elsewhere (controller helper)
#based on some fetched result i want to update my table
myId = #result.id
dataObj = MyModel.find(myId)
info = #result.information.gsub(',',' ') #some string operation
dataObj.update_attributes(:info, info)
end
end
I can use spawnling gem to perform extra operation after showing result to user. But i am just curious if i could do this with helper or other rails stuff.
While there are cases where it makes sense to use a background processing. This is not one of them.
I assume 'show result to user' means rendering some template based on the data fetched from the api.
Actually, it might be a good idea to move accessing the external API into background, but that would require changing the flow. As for the updating the db record, its generally not a good idea to move it to background.
that being said, I wouldn't do the update in the controller, I'd move it into the model or a 'mutation' class:
class Model
def self.update_from_api
res = API.fetch ...
object = find res.id
object.update_from_api! res
res
end
def update_from_api(api_data)
update_attributes! info: api_data.gsub(....)
end
end

Implement a sort as a class method in Ruby

ActiveRecord gives me a back a set of Users. I want to sort those users by a complex function that uses data not persisted in the User object.
Is there a way to use a scope when the data isn't persisted?
Is there a way to write a sort function as a class method, so I can make a call like:
sorted_users = User.first(20).sorted_my_way
i think it is not possible to do it this way.
it is possible to define class methods and scopes that can be called like User.first(20).sorted_my_way but those can only append stuff to the query you create. if you want to sort within ruby, i think that you need to wrap the whole stuff like:
class User
self.sorted_my_way(limit)
first(20).sort_by {...}
end
end
User.sorted_my_way(20)
another thing that you could do would be to use a block style method:
class User
self.sorted_my_way
yield.sort_by {...}
end
end
User.sorted_my_way { first(20) }

Does Rails have a per request store that I can add objects to and retrieve from?

In asp.net there is something called Request.Items that I can add an object to, and then I can check to see if an object is present in the .Items collection from another part of my code (like in another class).
Does Rails have something like this?
One of the most popular options is to use the request_store gem, which allows you to access a global store that you from any part of your code. It uses Thread.current to store your data, and takes care of cleaning up the data after each request.
RequestStore[:items] = []
Be aware though, since it uses Thread.current, it won't work properly in a multi-threaded environment where you have more than one thread per request.
To circumvent this problem, I have implemented a store that can be shared between threads for the same request. It's called request_store_rails, and the usage is very similar:
RequestLocals[:items] = []
You may define a class method (in any class you want) to store your data:
class Xyzzy
def self.items
#items ||= {}
end
end
# ....
if Xyzzy.items.include? :fubar
Xyzzy.items[:asd] = 1
end
But you should decide when you want this 'cache' to be cleared. If you want it only for one request, define a before_filter which will call Xyzzy.items.clear
The class objects are global. You may also use a standard global variable, but that would be less elegant. :)

Resources