Custom Adapter to support RocketPant with Rails - batman.js

I am using rocket_pants gem to build backend API https://github.com/Sutto/rocket_pants
It have a specific format to output data:
{
"response":[
{"id":1,"title":"Object Title","description":"Object Description"},
{"id":1,"title":"Object Title","description":"Object Description"} ],
"count":2,
"pagination": {
"previous":null,
"next":null,
"current":1,
"per_page":30,
"count":2,
"pages":1}
}
I am using Batman.RailsStorage to persist models. But actions like MyApp.Model.get('all') works fine on the backend but they actually do not parse and load model objects.
Can you guide me how to configure StorageAdapter or write new one to handle such kind of data format?

You could try overriding the collectionJsonNamespace method (defined on Batman.RestStorage).
I see that it's used after a readAll operation to get records from the HTTP response.
For example:
class MyApp.RocketPantsStorage extends Batman.RailsStorage
collectionJsonNamespace: -> "response"
Then in your model
#= require ../rocket_pants_storage
# the storage adapter must be available at load time
class MyApp.SomeModel
#persist MyApp.RocketPantsStorage
Does that work?

With the same approach mentioned in answer from #rmosolgo I build paginator as well.
class MyApp.RocketPantsPaginator extends Batman.ModelPaginator
totalCountKey: "pagination.count"
loadItemsForOffsetAndLimit: (offset, limit) ->
params = #paramsForOffsetAndLimit(offset, limit)
params[k] = v for k,v of #params
#model.load params, (err, records, env) =>
if err?
#markAsFinishedLoading()
#fire('error', err)
else
response = new Batman.Object(env.response)
#set('totalCount', response.get(#totalCountKey));
#updateCache(#offsetFromParams(params), #limitFromParams(params), records)

Related

How to pass a parameter from the Jupyter backend to a frontend extension

I currently have a value that is stored as an environment variable the environment where a jupyter server is running. I would like to somehow pass that value to a frontend extension. It does not have to read the environment variable in real time, I am fine with just using the value of the variable at startup. Is there a canonical way to pass parameters a frontend extension on startup? Would appreciate an examples of both setting the parameter from the backend and accessing it from the frontend.
[update]
I have posted a solution that works for nbextentions, but I can't seem to find the equivalent pattern for labextensions (typescript), any help there would be much appreciated.
I was able to do this by adding the following code to my jupter_notebook_config.py
from notebook.services.config import ConfigManager
cm = ConfigManager()
cm.update('notebook', {'variable_being_set': value})
Then I had the parameters defined in my extension in my main.js
// define default values for config parameters
var params = {
variable_being_set : 'default'
};
// to be called once config is loaded, this updates default config vals
// with the ones specified by the server's config file
var update_params = function() {
var config = Jupyter.notebook.config;
for (var key in params) {
if (config.data.hasOwnProperty(key) ){
params[key] = config.data[key];
}
}
};
I also have the parameters declared in my main.yaml
Parameters:
- name: variable_being_set
description: ...
input_type: text
default: `default_value`
This took some trial and error to find out because there is very little documentation on the ConfigManager class and none of it has an end-to-end example.

How to release or distribute an application that uses mikro-orm?

In the configuration I have to specify the paths to .js and .ts files defining entities:
MikroORM.init({
...
entitiesDirs: ["build/entities"],
entitiesDirsTs: ["src/entities"],
});
So, when I will go to release or distribute the application. Will I need distribute the typescript code too? or will I need distribute only the cache generated? or will I need distribute both? or... none?
As of MikroORM v2.2
Now you can work with default metadata provider, it will require entity source files only if you do not provide entity or type options in your decorators (you can use entity callback to use reference to entity class instead of using string name in type, handle for refactoring via IDE like webstorm).
Original answer:
You should ship the typescript code too, and let the cache regenerate on the server - cache would be rebuilt anyway as it checks absolute path to cached entity for invalidation.
You could implement your own cache adapter or metadata provider to get around this, if you don't want to ship the typescript code.
This is how you could implement custom metadata provider that simply throws error when the type option is missing:
import { MetadataProvider, Utils } from 'mikro-orm';
import { EntityMetadata } from 'mikro-orm/dist/decorators';
export class SimpleMetadataProvider extends MetadataProvider {
async loadEntityMetadata(meta: EntityMetadata, name: string): Promise<void> {
// init types and column names
Object.values(meta.properties).forEach(prop => {
if (prop.entity) {
prop.type = Utils.className(prop.entity());
} else if (!prop.type) {
throw new Error(`type is missing for ${meta.name}.${prop.name}`)
}
});
}
}
Then provide this class when initializing:
const orm = await MikroORM.init({
// ...
metadataProvider: SimpleMetadataProvider,
});
The value of type should be JS types, like string/number/Date... You can observe your cached metadata to be sure what values should be there.
Also keep in mind that without TS metadata provider, you will need to specify entity type in #ManyToOne decorator too (either via entity callback, or as a string via type).

Is it possible to configure everything within context?

I am trying to configure Audit.net and define my custom logic for saving logs.
Is there a way to configure included entities within context?
I tried this
`
public ResidentMasterContext(DbContextOptions options) : base(options)
{
AuditDataProvider = new DynamicDataProvider();
Mode = AuditOptionMode.OptIn;
IncludeEntityObjects = true;
EntitySettings = new Dictionary<Type, EfEntitySettings>
{
{typeof(Apartment), new EfEntitySettings()}
};
}
`
but OnScopeSaving is not firing. And when I change mode to OptOut it takes all entities
I guess you are referring to the Audit.NET EntityFramework extension.
if you use OptIn you need to mark the included entities with [AuditInclude] attribute, or use the Include methods of the fluent API. You can check the documentation here.
An example using the fluent API for the EF configuration, to include only the entities User and UserDetail:
Audit.EntityFramework.Configuration.Setup()
.ForContext<ResidentMasterContext>(config => config
.IncludeEntityObjects())
.UseOptIn()
.Include<User>()
.Include<UserDetail>();
An example of the output configuration:
Audit.Core.Configuration.Setup()
.UseDynamicProvider(_ => _.OnInsertAndReplace(auditEvent =>
{
Console.WriteLine(auditEvent.ToJson());
}));

Unit testing Symfony application using FOSElasticaBundle without an ES Server?

I have an application with an existing set of unit tests which are using SQLite as the DB. I have recently added search capabilities via ES which have replaced many of the endpoint actions that used to query the DB directly. I want to test all of the business logic involved with these endpoints without testing ES itself, which means no ES server available. I plan to test ES itself in a set of integration tests to be run less frequently.
My problem is trying to track down exactly what is going on with the execution flow.
My first inclination was to simply create a mock object of the ES Finder that FOSElasticaBundle creates for my index. Because I'm using pagination, it turned out to be more complex than I thought:
// code context: test method in unit test extending Symfony's WebTestCase
$client = $this->getClient();
$expectedHitCount = 10;
// Setup real objects which (as far as I can tell) don't act upon the ES client
// and instead only hold / manipulate the data.
$responseString = file_get_contents(static::SEARCH_RESULT_FILE_RESOURCE);
$query = SearchRepository::getProximitySearchQuery($lat, $lng, $radius, $offset, $limit);
$response = new Response($responseString, 200);
$resultSet = new RawPartialResults(new ResultSet($response, $query ));
// Create a mock pagination adapter which is what my service expects to be returned from
// the search repository.
$adapter = $this->getMockBuilder('FOS\ElasticaBundle\Paginator\RawPaginatorAdapter')
->disableOriginalConstructor()
->getMock();
$adapter->method('getTotalHits')->will($this->returnValue($expectedTotalCount));
$adapter->method('getResults')->will($this->returnValue($resultSet));
$adapter->method('getQuery')->will($this->returnValue($query));
$es = $this->getMockBuilder(get_class($client->getContainer()->get(static::ES_FINDER_SERVICE)))
->disableOriginalConstructor()
->getMock();
$es->method('createPaginatorAdapter')->will($this->returnValue($adapter));
// Replace the client container's service definition with our mock object
$client->getContainer()->set(static::ES_FINDER_SERVICE, $es);
This actually works all the way until I return the view from my controller. My service gets back the mock paginatior adapter with the pre-popuated result set from the JSON search response I have stored in a file (and subsequently passed into my ResultSet object). However, once I return the view, there seems to be a listener involved that tries to query ES again with the Query instead of using the ResultSet I already passed in.
I can't seem to find this listener. I also don't understand why it would try to query when a ResuletSet already exists.
I am using FOSRestBundle, as well, and making use of their ViewListener to auto-serialize whatever I return. I don't see any suspects in that flow, either. I think it may have something to do with the serialization of the result set, but so far haven't been able to track the offending code down.
Has anyone tried to do something similar to this before and have any suggestions on either how to debug my current setup or an alternative, better setup for mocking ES for this type of test?
After digging around I found an alternative solution that does not involve using mock objects. I am going to leave this open for the time being in case someone has a better approach, but the approach I decided to take in the mean time is to override the Client in my test environment.
FOSElasticaBundle has an example for overriding the client here: https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/cookbook/suppress-server-errors.md
I was able to override the client in such a way that I could create a unique key from the request and then provide responses based on that key, essentially stubbing the server for all known requests. For requests that don't match I return a default empty response. This works well enough for me.
Client Code
<?php
namespace Acme\DemoBundle\Tests\Elastica;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Client as BaseClient;
class Client extends BaseClient
{
/**
* This array translates a key which is the md5 hash of the Request::toString() into
* a human friendly name so that we can load the proper response from a file in the
* file system.
*
* #var array
*/
protected $responseLookup = array(
'7fea3dda860a424aa974b44f508b6678' => 'proximity-search-response.json'
);
/**
* {#inheritdoc}
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
$request = new Request($path, $method, $data, $query);
$requestKey = md5($request->toString());
$this->_log($request);
$this->_log("Test request lookup key: $requestKey");
if (!isset($this->responseLookup[$requestKey])
|| !$response = file_get_contents(__DIR__ . "/../DataFixtures/Resources/search/{$this->responseLookup[$requestKey]}")) {
return $this->getNullResponse();
}
return new Response($response);
}
public function getNullResponse()
{
$this->_log("Returning NULL response");
return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
Configuration Change
// file: config_test.yml
parameters:
fos_elastica.client.class: Acme\DemoBundle\Tests\Elastica\Client
Sample Response File (proximity-search-response.json)
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": null,
"hits": [
{
"_index": "search",
"_type": "place",
"_id": "1",
"_score": null,
"_source": {
"location": "40.849100,-73.644800",
"id": 1,
"name": "My Place"
},
"sort": [
322.52855474383045
]
}
]
}
}
This solution works well and is fast, but the maintenance is a pain. If anything about the request changes, you need to retrieve the new request key from the log, update it in the array, and update the file with the new response data for the new request. I generally just curl the server directly and modify it from there.
I would love to see any other solutions that may be simpler, but I hope this helps someone else in the meantime!
you can try to disable the event listeners in your config_test.yml (or whatever is your test environment name).
fos_elastica:
indexes:
your_index_name:
types:
your_type_name:
persistence:
listener:
insert: false
update: false
delete: false

ZF2 unit-testing authentication

I was learning about unit testing and I attempted to resolve the following issue:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for zfcUserAuthentication
... using the only answer given at:
Simple ZF2 Unit Tests for a controller using ZfcUser
So my setUp function looks the same. Unfortunately, I get the error message:
Zend\Mvc\Exception\InvalidPluginException: Plugin of type Mock_ZfcUserAuthentication_868bf824 is invalid; must implement Zend\Mvc\Controller\Plugin\PluginInterface
It is caused at this part of the code (split up in my code in the same way):
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock); // Error refers to this line.
The $authMock object is apparently not implementing plugininterface, which I need to implement to pass into setService.
Is $authMock not meant to be passed there for it's use in unit testing? Should I be using a different (unit-testing oriented) setService method?
I need a way to handle logging into my application, or my unit testing is pointless.
Thanks for any advice.
=== Edit (11/02/2013) ===
I wanted to focus on this part for clarification, as I think this is the problem area:
// Getting mock of authentication object, which is used as a plugin.
$authMock = $this->getMock('ZfcUser\Controller\Plugin\ZfcUserAuthentication');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
// At this point, PluginManager disallows mock being assigned as plugin because
// it will not implement plugin interface, as mentioned.
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
If the mock doesn't handle necessary implementations, how else am I to pretend to login?
You have a problem with name-spacing or your autoloader.
When you are creating your mock, the class definition of ZfcUser\Controller\Plugin\ZfcUserAuthentication is not being found. So PHPUnit creates a mock that only extends this class for your test. If the class was available then PHPUnit will use the actual class to extend when making its mock, which will then use the parent classes/interfaces.
You can see this logic here: https://github.com/sebastianbergmann/phpunit-mock-objects/blob/master/PHPUnit/Framework/MockObject/Generator.php
if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
!interface_exists($mockClassName['fullClassName'], $callAutoload)) {
$prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";
if (!empty($mockClassName['namespaceName'])) {
$prologue = 'namespace ' . $mockClassName['namespaceName'] .
" {\n\n" . $prologue . "}\n\n" .
"namespace {\n\n";
$epilogue = "\n\n}";
}
$cloneTemplate = new Text_Template(
$templateDir . 'mocked_clone.tpl'
);
So if there is no class or interface, PHPUnit will actually create one itself so that the mock will meet the type hinting of original class name. However, any parent classes or interfaces will not be included because PHPUnit is not aware of them.
This would be due to not including the proper namespace in your test or having a problem in your autoloader. It is difficult to tell without actually seeing the entire test file.
Alternatively rather than mocking ZfcUser\Controller\Plugin\ZfcUserAuthentication, you could mock the Zend\Mvc\Controller\Plugin\PluginInterface in your test and pass that into the plugin manager. Though if you are type-hinting for the plugin in your code, your test still won't work.
//Mock the plugin interface for checking authorization
$authMock = $this->getMock('Zend\Mvc\Controller\Plugin\PluginInterface');
// Some expectations of the authentication service.
$authMock -> expects($this->any())
-> method('hasIdentity')
-> will($this->returnValue(true));
$authMock -> expects($this->any())
-> method('getIdentity')
-> will($this->returnValue($ZfcUserMock));
$this -> controller->getPluginManager()
->setService('zfcUserAuthentication', $authMock);
I just made an example for the FlashMessenger plugin. You should just use the ControllerPluginManager to override the ControllerPlugin. Make sure that your application bootstrap calls setApplicationConfig();
<?php
namespace SimpleTest\Controller;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class SimpleControllerTest extends AbstractHttpControllerTestCase {
public function testControllerWillAddErrorMessageToFlashMessenger()
{
$flashMessengerMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\FlashMessenger', array('addErrorMessage'))->getMock();
$flashMessengerMock->expects($this->once())
->method('addErrorMessage')
->will($this->returnValue(array()));
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->get('ControllerPluginManager')->setService('flashMessenger', $flashMessengerMock);
$this->dispatch('/error/message');
}
}?>

Resources