An application allows a user to conduct various operations which generate session data. Then the user may want to execute a transaction based on that data. A login or sign-up is necessary.
Unfortunately, the session controller in Devise creates a new session, throwing away all that data. I have not found a concise technique that allows to maintain the existing session data, as this is preferred to writing data to the cookie.
after_sign_in_path_for(resource) only allows the user to return to a previous page
Context rails 3.2.18, devise 2.2.4
Overriding the session controller is possible. Given that this context has 2 contextually different entry points (generic_url vs confirm_transaction_url), the following various param settings can be carried over and the user brought back to the previous page s/he was on
class SessionsController < Devise::RegistrationsController
def destroy
param1 = session[:param1] unless session[:param1].nil?
param2 = session[:param2] unless session[:param2].nil?
confirm_url = session[:confirm_url] unless session[:confirm_url].nil?
previous_url = session[:previous_url] unless session[:previous_url].nil?
super
session[:param1] = param1
session[:param2] = param2
session[:confirm_url] = confirm_url
session[:previous_url] = previous_url
end
end
The applications controller will route the user
def after_sign_in_path_for(resource)
session[:previous_url] || session[:confirm_url]
end
Answer posted for indexing purposes. Thanks to pointer from onemanarmy
Related
I have a simple structure of "A has_many B has_many C"
If I go into Rails console and do something like A.first.Bs.first.C.create() it'll create without an issue, however, if I use the API (or even Seeds actually) and so something like POST to /api/v1/a/1/b with the below create, I will always get rejected due to "Must belong to A" - Basically meaning it's trying to save as a.id = null.
A = Campaign. B = Party for the below snippet.
def create
#campaign = Campaign.find_by_id(params[:campaign_id])
if #campaign.user_id == current_user.id
#party = Party.new(party_params)
# #party.campaign_id = params[:campaign_id]
if #party.save!
render status: 201, json: {
message: "Successfully saved the party!",
party: #party,
user: current_user
}
else
render status: 404, json: {
message: "Something went wrong: Check line 27 of Party Controller"
}
end
end
end
The line I have commented out where I manually assigned #party.campaign_id resolved the error, but I am curious why it doesn't automatically pull from the information? Do route resources not function the same way as a Campaign.first.parties.create would?
Welcome any revision to this create method; It feels bulky, and likely not secure at all presently.
(Note #campaign.user_id == current_user.id is kind of a generic catch in case someone is trying to update someone else's campaign. I will likely re-visit this logic to make it more secure.)
Rails does not find anything automatically basing on routes, you need to do it by yourself.
In this case you can either assign id basing on params (as you did in the comment) or build Party as an element of Campaign.parties association
#campaign = Campaign.find_by_id(params[:campaign_id])
#party = #campaign.parties.new(party_params)
I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)
I'm building a Telegram bot that uses ConversationHandler to prompt the user for a few parameters and settings about how the bot should behave. This information is stored in some global variables since it needs to be available and editable by different functions inside the program. Every global variable is a dictionary in which each user is associated with its own value. Here's an example:
language = {123456: 'English', 789012: 'Italian'}
where 123456 and 789012 are user ids obtained from update.message.from_user.id inside each function.
After all the required information has been received and stored, the bot should send a message containing a text fetched from a web page; the text on the web page is constantly refreshed, so I want the message to be edited every 60 seconds and updated with the new text, until the user sends the command /stop.
The first solution that came to my mind in order to achieve this was something like
info_message = bot.sendMessage(update.message.chat_id, text = "This message will be updated...")
...
def update_message(bot, update):
while True:
url = "http://example.com/etc/" + language[update.message.from_user.id]
result = requests.get(url).content
bot.editMessageText(result, chat_id = update.message.chat_id, message_id = info_message.message_id)
time.sleep(60)
Of course that wouldn't work at all, and it is a really bad idea. I found out that the JobQueue extension would be what I need. However, there is something I can't figure out.
With JobQueue I would have to set up a callback function for my job. In my case, the function would be
def update_message(bot, job):
url = "http://example.com/etc/" + language[update.message.from_user.id]
result = requests.get(url).content
bot.editMessageText(result, chat_id = update.message.chat_id, message_id = info_message.message_id)
and it would be called every 60 seconds. However this wouldn't work either. Indeed, the update parameter is needed inside the function in order to fetch the page according to the user settings and to send the message to the correct chat_id. I'd need to pass that parameter to the function along with bot, job, but that doesn't seem to be possible.
Otherwise I would have to make update a global variable, but I thought there must be a better solution. Any thoughts? Thanks.
I had the same issue. A little digging into the docs revealed that you can pass job objects a context parameter which can then be accessed by the callback function as job.context.
context (Optional[object]) – Additional data needed for the callback function. Can be accessed through job.context in the callback. Defaults to None
global language
language = {123456: 'English', 789012: 'Italian'}
j=updater.job_queue
context={"chat_id":456754, "from_user_id":123456, "message_id":111213}
update_job = job(update_message, 60, repeat=True, context=context)
j.put(update_job, next_t=0.0)
def update_message(bot, job):
global language
context=job.context
url = "http://example.com/etc/" + language[context["from_user_id"]]
result = requests.get(url).content
bot.editMessageText(result,
chat_id = context["chat_id"],
message_id = context["message_id"])
It's a pretty standard task in Django REST Framework to supply additional args/kwargs to a serializer to set values of fields set not via request.data, but via the value in url parameters or cookies. For instance, I need to set user field of my Comment model equal to request.user upon POST request. Those additional arguments are called context.
Several questions (1, 2) on StackOverflow suggest that I override get_serializer_context() method of my ModelViewSet. I did and it doesn't help. I tried to understand, what's wrong, and found out that I don't understand from the source code, how this context system is supposed to work in general. (documentation on this matter is missing, too)
Can anyone explain, where serializer adds context to normal request data? I found two places, where it saves the values from context.
serializer.save(), method, which mixes kwargs with validated data, but it is usually called with no arguments (e.g. by ModelMixins).
fields.__new__(), which caches args and kwargs, but it seems that nobody ever reads them later.
Whenever you use generic views or viewsets, DRF(3.3.2) adds request object, view object and format to the serializer context. You can use serializer.context to access, lets say request.user in the serializer.
This is added when get_serializer_class() is called. Inside that, it calls get_serializer_context() method where all these parameters are added to its context.
DRF source code for reference:
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
to set values of fields set not via request.data, but via the value in url parameters or cookies. For instance, I need to set user field of my Comment model equal to request.user upon POST request.
This is how I handle both cases in my ModelViewSet:
def perform_create(self, serializer):
# Get article id from url e.g. http://myhost/article/1/comments/
# obviously assumes urls.py is setup right etc etc
article_pk = self.kwargs['article_pk']
article = get_object_or_404(Article.objects.all(), pk=article_pk)
# Get user from request
serializer.save(author=self.request.user, article=article)
Unfortunately the nested objects is not standard for DRF but that's besides the point. :)
My app has an expensive service method, results of which must be 1) checked for errors and 2) presented to a Java applet via a URL (i.e. as opposed to a JavaScript variable). The method result is a string, and the applet is only capable of loading data from a file or URL.
I tried to deal with the problem using a session variable:
def action1 = {
def input = params['input']
def result = expensiveServiceMethod( input )
def failed = result == null
session['result'] = result
render( view:'view1', model:[failed:failed] )
}
def action2 = {
def result = session['result']
render( result )
}
Then, in view1 the applet is conditionally displayed depending on the failure status, and the results are accessed by the applet via the action2 URL.
Unfortunately, result in action2 is coming up null. I've verified that result is not null in action1. Am I doing it wrong?
Note
I would have used flash instead, but there are additional requests made in order to initialize the applet.
Applets aren't able to track session cookies their self. So when your applet sends second request to action2 - it does't send session cookie back to server. Hence for server its like a brand new session, any thing you set in session during action1 won't be available in action2. You will have to track cookies in your applet and send them back to server when making calls.
I have never done it, but I think you may use Apache commons http client on your client side (applet) - it has support for tracking cookies
See this question -