-
-
Save pixelastic/17ec843f8e36f97d295d9602f0d318ed to your computer and use it in GitHub Desktop.
# Answer | |
Thanks for the question, I'll do my best to guide you the best way I can. | |
I think you're very close to have a working implementation. | |
Algolia secured API keys are API keys with *hard* filters already embedded | |
inside. You generic Search API key will let you search through all the content | |
in your index, but when you create a secured API key, you create a specific | |
version of your search API key that can only search into a subset of records. | |
You can think of it as having already some filters checked (search only for | |
records with this attribute, or with a price above a threshold, etc). And there | |
is no way to remove those filters. It's perfect for multitenancy, when you don't | |
want user A to have access to data of user B. | |
In your case, you have each Report that belongs to a Company. If you're | |
currently loggued in as User A (which is part of Company 42), you'll want | |
a secured API key that can only access reports that belongs to Company 42. You | |
would then call `Algolia.generate_secured_api_key 'YourSearchAPIKey', {'filters' | |
=> 'company_id:42'}`. | |
In that example, every User of Company 42 will use the same key. Generating | |
those keys can be done entirely offline (meaning, you don't need to contact the | |
Algolia servers to do so), and is idempotent (generating a new key with the same | |
parameters, will always return the same value). | |
The key you generated that way should then be used to contact our servers, and | |
used as the `apiKey` in InstantSearch. I suggest that you generate this key in | |
your controller and pass it directly to your view. You don't need to store it in | |
your database, you can simply regenerate it everytime you need it. | |
So, to answer your questions more specifically: | |
- Where does public_key = Algolia.generate_secured_api_key | |
'YourSearchOnlyApiKey', {'filters'=> '_tags:user_42'} go? In my Report model? | |
This should go in your controller, and you should pass the result to your view, | |
to populate the `apiKey` of InstantSearch, instead of | |
`ENV['ALGOLIASEARCH_API_KEY_SEARCH']`. This secured API key is more specific | |
than tha generic Search API key, as it already has some filters embedded inside. | |
- Do I need to add a migration to my Report model to include a public_key? | |
No. As you're not storing the key in your model, you don't need to. Just | |
generate the key each time you need it. You *can* if you want generate it once | |
and then store it, but it seems overkill. | |
- To display Reports where and indexed Report.company_id | |
= current_user.company.id what do I put in the 'filters' section? Or is it not | |
a filter? | |
It is a filter :). In it, you should follow this syntax: | |
`{attribute_name}:{value}`. So if you want only the Reports that belong to | |
a specific company, you should add `company_id:42`. If you want only the one | |
that belong to a specific user, it would be `user_id:42`. Note that you must | |
have `company_id`/`user_id` defined as `attributesForFaceting` in your settings | |
for this to work. | |
- Do I need to change anything in my index.html.erb view to display? | |
Yes, you need to get the secured API key and pass it instead of the `apiKey`. | |
... | |
$(document).ready(function() { | |
var hitTemplate = '<a href="reports/{{{slug}}}" class="hit-report-card">' + | |
'<div id="preview_image" class="hit-report-img" style="content:url(<%= @attachments.find(6).image_url(:preview) %>);">ATTACHMENT ID: {{attachment.id}}</div>' + | |
'<div class="hit-report-title dont-break-out">{{{_highlightResult.title.value}}}</div>' + | |
'<div class="hit-report-date">{{{date_in_words}}}</div>' + | |
'<div class="hit-report-user">{{user.first_name}} {{user.last_name}}</div>' + | |
'<div class="hit-report-type">A/B Test</div>' + | |
'</a>'; | |
var search = instantsearch({ | |
appId: '<%= ENV[ 'ALGOLIASEARCH_APPLICATION_ID'] %>', | |
apiKey: '<%= ENV['ALGOLIASEARCH_API_KEY_SEARCH'] %>', | |
indexName: '<%= Report %>', | |
urlSync: true | |
}); | |
search.addWidget( | |
instantsearch.widgets.searchBox({ | |
container: '#q', | |
placeholder: 'Search for reports', | |
autofocus: false, | |
poweredBy: true | |
}) | |
); | |
... |
DISCLAIMER: | |
Sorry if this is a little long winded but I just want to ensure I communicate it properly (also I'm still learning Ruby & Rails so sorry if some issues are Rails questions than Algolia specific). | |
MY APP: | |
My Rails app has the following models: | |
- Company (has_many :users, has_many :reports) | |
- User (belongs_to :company, has_many :reports) | |
- Report (belongs_to :company, belongs_to :user) | |
I have managed to successfull indexed my Report model along with some nested attributes as shown below in the report.rb file. | |
I read this: https://discourse.algolia.com/t/multi-tenant-algolia-index/147 and a few other articals that kind of gave me the impression | |
that the best way to multitenancy with Algolia was to add the company_id to the indexed Reports model and then user the generate_secured_api_key | |
to filter results from the backend so a user only see's their companies reports in the search results. | |
QUESTIONS: | |
- Where does public_key = Algolia.generate_secured_api_key 'YourSearchOnlyApiKey', {'filters'=> '_tags:user_42'} go? In my Report model? | |
- Do I need to add a migration to my Report model to include a public_key? | |
- To display Reports where and indexed Report.company_id = current_user.company.id what do I put in the 'filters' section? Or is it not a filter? | |
- Do I need to change anything in my index.html.erb view to display? |
... | |
# Algolia Integration - https://www.algolia.com/doc/api-client/rails/usage/ | |
include AlgoliaSearch | |
algoliasearch do | |
# all attributes will be sent | |
attribute :id, :slug, :title, :overview, :result, :company_id, :user_id, :created_at, :updated_at, :date_in_words | |
attribute :user do | |
# Adds a nested model for User but restricts to first_name + last_name + email | |
{ first_name: user.first_name, last_name: user.last_name, email: user.email } | |
end | |
attribute :attachment do | |
# Adds a nested model for Attachment | |
{ id: attachment_ids } | |
end | |
end | |
... |
No way, I was so close! Haha! I tried that but was using single quotes, just presumed that syntax couldn't be used there with it not working. Thanks again!
One more question and I promise I'll stop pestering you after this, haha! If you don't mind.. please 😄
QUESTION:
Each Report (that's indexed with Algolia) will have a number of attachments (I'm using Amazon S3 to store them), due to the AWS permissions I have set I can't index the attachment url (as they time out) so have indexed the attachment ID's then get the url from that.
As you can see below I have <%= @attachments.find(6).image_url(:preview) %> which displays the same attachment (attachment.id = 6) image for each indexed Report displayed in the results. I wish to show just the first attachment id from the indexed array of attachment ids for each of the indexed reports.
The attachment.ids are indexed as a nested element within report. I'm mainly confused as it's switching from .erb to js to ruby then trying to get the first id from the Algolia index instead of just attachment.id 6.
INDEXED DATA
- nested attachment array of id's within the report:
"attachment": {
"id": [
167,
168,
166
]
}
INDEX.HTML>ERB
var hitTemplate = '<a href="reports/{{{slug}}}" class="hit-report-card">' +
'<div id="preview_image" class="hit-report-img" style="content:url(<%= @attachments.find(6).image_url(:preview) %>);">ATTACHMENT ID: {{attachment.id}}</div>' +
'<div class="hit-report-title dont-break-out">{{{_highlightResult.title.value}}}</div>' +
'<div class="hit-report-date">{{{date_in_words}}}</div>' +
'<div class="hit-report-user">{{user.first_name}} {{user.last_name}}</div>' +
'<div class="hit-report-type">A/B Test</div>' +
'</a>';
...
I hope that make sense - I promise that's it now. I'll do my best to sort out pagination and other stuff without you. haha! Thank you so much for your help with all this.
In Ruby, you can use
"#{}"
for variable interpolation.So you can follow the same logic to set:
Be careful, it only works in double quotes (
"
), not single quotes ('
).Hope that helps :)