Searchkick: how does search_index.refresh work with async callbacks? - searchkick

In a typical Rails controller, we create a Post model and then redirect to posts_path. The index action uses Searchkick to list Posts.
# In the model
class Post < ApplicationRecord
searchkick callbacks: :async
end
# In the controller
def index
#posts = Post.search("*")
end
def create
Post.create!(post_params)
redirect_to posts_path
end
However, since Elasticsearch is eventually consistent, sometimes the redirect occur before Searchkick/Elasticsearch indexed that record.
Searchkick's docs state that one could use Post.search_index.refresh to wait for the index to catch up with the new record. So one could write:
def create
Post.create!(post_params)
Post.search_index.refresh
redirect_to posts_path
end
However, sometimes we still redirect to /posts without seeing the newly created record; I think that it's because we're using callbacks: :async, so Searchkick (and Elasticsearch::API::Indices::IndicesClient which is used behind the scenes) has no way to know about the queued Searchkick::ReindexV2Job.
If that's the case, how does one solve this use-case, which is: after the #create or #destroy action have created/destroyed a Searchkick-indexed record, redirect to the #index action making sure that the record is already on the index, when using async callbacks?

Related

pyTelegramBotAPI pagination: how to pass a data between pages in a message

I have a Telegram bot for the forwarding of database requests. It returns long messages, so,
I use pagination from python-telegram-bot-pagination, for splitting this message to blocks. But I can't find a way to split the message to blocks effectively.
Now it looks like this:
by user request, the #bot.message_handler runs send_info_to_user function.
send_info_to_user calls the some_long_operation and gets the whole dataset as a result.
send_info_to_user gets a slice from some_long_operation result and sends it as a message for user
in case of using pagination inline buttons, the #bot.callback_query_handler will call send_info_to_user again
it re-calls the some_long_operation and gets the whole dataset again, just for another slice from the result and edit the content of the previous message.
# a time-consumable function
def some_long_operation(user_id):
return [f'some_text{i}\n{user_id}' for i in range(40)]
# send info to user by pages
def send_info_to_user(message, user_id, isCallback=False, page=1):
result = some_long_operation(user_id)
paginator = InlineKeyboardPaginator(
len(answer),
current_page=page,
data_pattern=f'{user_id}#'+'items#{page}' # wrap user_id for callback
)
if isCallback:
bot.edit_message_text(message.chat.id, result[page-1], message.message_id, paginator.markup)
else:
bot.send_message(message.chat.id, result[page-1],paginator.markup)
#bot.message_handler(content_types='text')
def message_reply(message):
send_info_to_user(message, message.from_user.id)
# parse callback from inline buttons
#bot.callback_query_handler(func=lambda call: call.data.split('#')[1]=='items')
def items_page_callback(call):
page = int(call.data.split('#')[2])
user_id = call.data.split('#')[0] # unwrap user_id
get_items(call.message, user_id, True, page)
Obviously, re-call of some_long_operation every time and using a slice of it isn't a good solution. But I can't find a way to pass the data between pages in a message.
The result of some_long_operation is user-specific and changes in time, so, store it anywhere isn't a good idea.
As I think, the some_long_operation should be called once, the result of it should be available as a slices for different pages of the message.

How to login with user but still stay admin?

I want to implement feature when operator/admin may login as user. Do something under user's credentials and then return back and continue as operator/admin
I try to mount whole application under /as_user/:user_id route. So when request come I adjust session to :user_id.
I try detour
$app->routes->route( '/as_user/:app_user' )->detour( app => $app );
But in this case when GET /as_user/17/packages request come the application fall into infinite loop
Also I think to append ?U=17 query parameter. But I do not know how and where rewrite code in such way: All link should be rendered with ?U=17 appended.
Please advice how to login with another user but still stay admin.
Seems I found the answer:
$r->under( '/as_user/:user_id', sub{
# FIX THE SESSION HERE. Just like:
# $_[0]->session->{ user_id } = _[0]->match->stack->[-1]->{ user_id };
return 1; # Required to not break the dispatch chain
})->route('/')->detour( 'App' );
Instead of application instance you should pass application class and Mojolicious will instantiate it itself.
PS. Infinite loop maybe because of cyclic refs. (But Mojolicious check refs here)
UPD
Infinite loop because of bug

2 separate controllers for the same end point in html and json or a single one?

I have the end points "/customers" and "/api/v1/customers", in html and json respectively for a list of customers. Do I have to create 2 different controllers and thus actions for them? Or can I return html or json from a single controller and action depending a requested format: html or json? Note that for "/api/v1/customers" I need authentication via an Api Key.
You can have one controller and action for both endpoints, but I would advise against it.
You mentioned that those controllers need to do different stuff, so instead of adding stuff like "if json then check api key" make two separate controllers and extract common code of getting all the customers.
There is a great talk about untangling business logic from http interface: http://www.elixirconf.eu/elixirconf2016/lance-halvorsen Getting a list of customers might be out of your controllers, so at the end you will have two controllers like this:
defmodule MyApp.Api.CustomersController do
plug MaApp.ApiAuth #plug for checking api key
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
def MyApp.CustomersController do
plug MyApp.UserAuth #for example checks if user is logged in
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
At the end your controller does not perform any logic, it calls something else to do the job and action is responsible only for web stuff like parsing params, authentication via api key, session cookies and translating end result to json/html.

First argument in form cannot contain nil or be empty in rails 4.0

There are tons of these, especially for Rails 4.0 and Rails 3.x. I am new to nested routing and I find it very difficult, especially with forms.
So I have the following:
Routes
resources :users do
resources :api_keys, path: '/developmentcenter'
end
The relationship here is: user has many api keys where api key belongs to user.
Controller
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def new
#user = User.find(params[:user_id])
#api_key = ApiKey.new(:user => #user)
end
def index
#user = User.find(params[:user_id])
#api_key = #user.api_keys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
#user = User.find(params[:user_id])
destroy_api_key(#user)
end
private
def create_new_api_key
params.permit(:api_key, user_attributes: [:id])
end
end
The above is pretty basic. create_api_key is a method that does something on save, or does something else on failure to save. While destroy_api_key Just find the api key based on the user id and deletes it, does something on success and something on failure.
So now lets create a form - which has a single button for creating the API key.
<h2>Create a new Key</h2>
<%= form_for ([#user, #api_keys]) do |f| %>
<p class="button"><%= f.submit "Generate API Key" %></p>
<% end %>
All we need is a single submit button that upon click, creates a new api key for the user whom is logged in.
But wait we have a an error:
First argument in form cannot contain nil or be empty
This error takes place at:
<%= form_for ([#user, #api_keys]) do |f| %>
So - I have looked at every single one of the stack questions (well most) that deal with this error, I have changed my controller based on a few, to what you see above. I have even look at the form helpers docs in the rails manual.
I cannot figure this out.
It is telling you that #user is empty or nil in the context it is using. Either this is a user who has not been created in the DB yet, or your User.find call is not working to return an actual user. The form needs to know who #user is before it can create a nested resource (#api_key) for it. Your `create_api_key' is completely wrong. You need to whitelist your params first, then find the user in the DB (or create them), then you can use the #user instance variable to create a form for that user to create a key. I think if you do it right, you shouldn't need to call #api_keys in the beginning of the form if you defined the relationships in your models (has_many or has_one, belongs_to etc.). Can you post the web server console output when you visit that page? First off you are calling
#user = User.find(params[:user_id])
every time in your controller. You should DRY that up with a before_action.
private
def set_user
#user = User.find(api_key_params[:user_id])
end
Then at the top of the controller you would have:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
before_action: :set_user
Also you should make your .permit a method that returns a variable called api_key_params:
def api_key_params
params.require(:user).permit(:api_key)
end
That way you have the things you want returned in a whitelist. To access these params you just call the method. You can specify a param you want returned from the method like my set_user example. But it also gives you the ability to do things like:
def create
#api_key = Api_key.new(api_key_params)
end
That way the Api_key.new gets the user and creates the key. You don't show how or where you generate the key itself. If it gets created by some method, you should put that in the model for Api_key. There are some other things about your code that are confusing without seeing the rest of your files.

Sail.js - routing to methods, custom policies & PATCH method

I have a few questions that I couldn't find answers anywhere online.
Does sails.js framework support HTTP PATCH method? If not - does anyone know if there is a planned feature in the future?
By default if I create method in a controller it is accessible with GET request is it the routes.js file where I need to specify that method is accessible only via POST or other type of methods?
How would you create a policy that would allow to change protected fields on entity only for specific rights having users. I.e: user that created entity can change "name", "description" fields but would not be able to change "comments" array unless user is ADMIN?
How would you add a custom header to "find" method which specifies how many items there are in database? I.e.: I have /api/posts/ and I do query for finding specific items {skip: 20; limit: 20} I would like to get response with those items and total count of items that would match query without SKIP and LIMIT modifiers. One thing that comes to my mind is that a policy that adds that that custom header would be a good choice but maybe there is a better one.
Is there any way to write a middle-ware that would be executed just before sending response to the client. I.e.: I just want to filter output JSON not to containt some values or add my own without touching the controller method.
Thank you in advance
I can help with 2 and 5. In my own experience, here is what I have done:
2) I usually just check req.method in the controller. If it's not a method I want to support, I respond with a 404 page. For example:
module.exports = {
myAction: function(req, res){
if (req.method != 'POST')
return res.notFound();
// Desired controller action logic here
}
}
5) I create services in api/services when I want to do this. You define functions in a service that accept callbacks as arguments so that you can then send your response from the controller after the service function finishes executing. You can access any service by the name of the file. For example, if I had MyService.js in api/services, and I needed it to work with the request body, I would add a function to it like this:
exports.myServiceFunction = function(requestBody, callback){
// Work with the request body and data access here to create
// data to give back to the controller
callback(data);
};
Then, I can use this service from the controller like so:
module.exports = {
myAction: function(req, res){
MyService.myServiceFunction(req.body, function(data){
res.json(data);
});
}
}
In your case, the data that the service sends back to the controller through the callback would be the filtered JSON.
I'm sorry I can't answer your other questions, but I hope this helps a bit. I'm still new to Sails.js and am constantly learning new things, so others might have better suggestions. Still, I hope I have answered two of your questions.