DS.ActiveModelSerializer Class
The ActiveModelSerializer is a subclass of the RESTSerializer designed to integrate
with a JSON API that uses an underscored naming convention instead of camelCasing.
It has been designed to work out of the box with the
active_model_serializers
Ruby gem. This Serializer expects specific settings using ActiveModel::Serializers,
embed :ids, embed_in_root: true
which sideloads the records.
This serializer extends the DS.RESTSerializer by making consistent use of the camelization, decamelization and pluralization methods to normalize the serialized JSON into a format that is compatible with a conventional Rails backend and Ember Data.
JSON Structure
The ActiveModelSerializer expects the JSON returned from your server to follow the REST adapter conventions substituting underscored keys for camelcased ones.
Conventional Names
Attribute names in your JSON payload should be the underscored versions of the attributes in your Ember.js models.
For example, if you have a Person
model:
App.FamousPerson = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
occupation: DS.attr('string')
});
The JSON returned should look like this:
{
"famous_person": {
"id": 1,
"first_name": "Barack",
"last_name": "Obama",
"occupation": "President"
}
}
Let's imagine that Occupation
is just another model:
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
occupation: DS.belongsTo('occupation')
});
App.Occupation = DS.Model.extend({
name: DS.attr('string'),
salary: DS.attr('number'),
people: DS.hasMany('person')
});
The JSON needed to avoid extra server calls, should look like this:
{
"people": [{
"id": 1,
"first_name": "Barack",
"last_name": "Obama",
"occupation_id": 1
}],
"occupations": [{
"id": 1,
"name": "President",
"salary": 100000,
"person_ids": [1]
}]
}
Item Index
Methods
- _canSerialize
- _getMappedKey
- applyTransforms
- extract
- extractArray
- extractCreateRecord
- extractDeleteRecord
- extractErrors
- extractFind
- extractFindAll
- extractFindBelongsTo
- extractFindHasMany
- extractFindMany
- extractFindQuery
- extractMeta
- extractSave
- extractSingle
- extractUpdateRecord
- keyForAttribute
- keyForRelationship
- normalize
- normalizeAttributes
- normalizeErrors
- normalizeId
- normalizeLinks
- normalizePayload
- normalizeRelationships
- normalizeUsingDeclaredMapping
- pushPayload
- serialize
- serializeAttribute
- serializeBelongsTo
- serializeHasMany
- serializeIntoHash
- serializePolymorphicType
- transformFor
- typeForRoot
Properties
Methods
_canSerialize
-
key
Check attrs.key.serialize property to inform if the key
can be serialized
Parameters:
-
key
String
Returns:
true if the key can be serialized
_getMappedKey
-
key
Looks up the property key that was set by the custom attr
mapping
passed to the serializer.
Parameters:
-
key
String
Returns:
key
applyTransforms
-
type
-
data
Given a subclass of DS.Model
and a JSON object this method will
iterate through each attribute of the DS.Model
and invoke the
DS.Transform#deserialize
method on the matching property of the
JSON object. This method is typically called after the
serializer's normalize
method.
Parameters:
-
type
subclass of DS.Model -
data
ObjectThe data to transform
Returns:
data The transformed data object
extract
-
store
-
type
-
payload
-
id
-
requestType
The extract
method is used to deserialize payload data from the
server. By default the JSONSerializer
does not push the records
into the store. However records that subclass JSONSerializer
such as the RESTSerializer
may push records into the store as
part of the extract call.
This method delegates to a more specific extract method based on
the requestType
.
Example
var get = Ember.get;
socket.on('message', function(message) {
var modelName = message.model;
var data = message.data;
var type = store.modelFor(modelName);
var serializer = store.serializerFor(type.typeKey);
var record = serializer.extract(store, type, data, get(data, 'id'), 'single');
store.push(modelName, record);
});
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractArray
-
store
-
primaryType
-
payload
Called when the server has returned a payload representing
multiple records, such as in response to a findAll
or findQuery
.
It is your opportunity to clean up the server's response into the normalized form expected by Ember Data.
If you want, you can just restructure the top-level of your payload, and
do more fine-grained normalization in the normalize
method.
For example, if you have a payload like this in response to a request for all posts:
{
"_embedded": {
"post": [{
"id": 1,
"title": "Rails is omakase"
}, {
"id": 2,
"title": "The Parley Letter"
}],
"comment": [{
"_id": 1,
"comment_title": "Rails is unagi"
"post_id": 1
}, {
"_id": 2,
"comment_title": "Don't tread on me",
"post_id": 2
}]
}
}
You could implement a serializer that looks like this to get your payload into shape:
App.PostSerializer = DS.RESTSerializer.extend({
// First, restructure the top-level so it's organized by type
// and the comments are listed under a post's comments
key.
extractArray: function(store, type, payload) {
var posts = payload._embedded.post;
var comments = [];
var postCache = {};
posts.forEach(function(post) {
post.comments = [];
postCache[post.id] = post;
});
payload._embedded.comment.forEach(function(comment) {
comments.push(comment);
postCache[comment.post_id].comments.push(comment);
delete comment.post_id;
});
payload = { comments: comments, posts: payload };
return this._super(store, type, payload);
},
normalizeHash: {
// Next, normalize individual comments, which (after extract
)
// are now located under comments
comments: function(hash) {
hash.id = hash._id;
hash.title = hash.comment_title;
delete hash._id;
delete hash.comment_title;
return hash;
}
}
})
When you call super from your own implementation of extractArray
, the
built-in implementation will find the primary array in your normalized
payload and push the remaining records into the store.
The primary array is the array found under posts
.
The primary record has special meaning when responding to findQuery
or findHasMany
. In particular, the primary array will become the
list of records in the record array that kicked off the request.
If your primary array contains secondary (embedded) records of the same type,
you cannot place these into the primary array posts
. Instead, place the
secondary items into an underscore prefixed property _posts
, which will
push these items into the store and will not affect the resulting query.
Parameters:
-
store
DS.Store -
primaryType
subclass of DS.Model -
payload
Object
Returns:
The primary array that was returned in response to the original query.
extractCreateRecord
-
store
-
type
-
payload
-
id
-
requestType
extractCreateRecord
is a hook into the extract method used when a
call is made to DS.Store#createRecord
. By default this method is
alias for extractSave.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractDeleteRecord
-
store
-
type
-
payload
-
id
-
requestType
extractDeleteRecord
is a hook into the extract method used when
a call is made to DS.Store#deleteRecord
. By default this method is
alias for extractSave.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractErrors
-
store
-
type
-
payload
-
id
extractErrors
is used to extract model errors when a call is made
to DS.Model#save
which fails with an InvalidError. By default
Ember Data expects error information to be located on the
errors`
property of the payload object.
Example
App.PostSerializer = DS.JSONSerializer.extend({
extractErrors: function(store, type, payload, id) {
if (payload && typeof payload === 'object' && payload._problems) {
payload = payload._problems;
this.normalizeErrors(type, payload);
}
return payload;
}
});
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number
Returns:
json The deserialized errors
extractFind
-
store
-
type
-
payload
-
id
-
requestType
extractFind
is a hook into the extract method used when
a call is made to DS.Store#find
. By default this method is
alias for extractSingle.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractFindAll
-
store
-
type
-
payload
-
id
-
requestType
extractFindAll
is a hook into the extract method used when a
call is made to DS.Store#findAll
. By default this method is an
alias for extractArray.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
array An array of deserialized objects
extractFindBelongsTo
-
store
-
type
-
payload
-
id
-
requestType
extractFindBelongsTo
is a hook into the extract method used when
a call is made to DS.Store#findBelongsTo
. By default this method is
alias for extractSingle.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractFindHasMany
-
store
-
type
-
payload
-
id
-
requestType
extractFindHasMany
is a hook into the extract method used when a
call is made to DS.Store#findHasMany
. By default this method is
alias for extractArray.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
array An array of deserialized objects
extractFindMany
-
store
-
type
-
payload
-
id
-
requestType
extractFindMany
is a hook into the extract method used when a
call is made to DS.Store#findMany
. By default this method is
alias for extractArray.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
array An array of deserialized objects
extractFindQuery
-
store
-
type
-
payload
-
id
-
requestType
extractFindQuery
is a hook into the extract method used when a
call is made to DS.Store#findQuery
. By default this method is an
alias for extractArray.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
array An array of deserialized objects
extractMeta
-
store
-
type
-
payload
extractMeta
is used to deserialize any meta information in the
adapter payload. By default Ember Data expects meta information to
be located on the meta
property of the payload object.
Example
App.PostSerializer = DS.JSONSerializer.extend({
extractMeta: function(store, type, payload) {
if (payload && payload._pagination) {
store.metaForType(type, payload._pagination);
delete payload._pagination;
}
}
});
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object
extractSave
-
store
-
type
-
payload
-
id
-
requestType
extractSave
is a hook into the extract method used when a call
is made to DS.Model#save
. By default this method is alias
for extractSingle.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
extractSingle
-
store
-
primaryType
-
payload
-
recordId
Called when the server has returned a payload representing
a single record, such as in response to a find
or save
.
It is your opportunity to clean up the server's response into the normalized form expected by Ember Data.
If you want, you can just restructure the top-level of your payload, and
do more fine-grained normalization in the normalize
method.
For example, if you have a payload like this in response to a request for post 1:
{
"id": 1,
"title": "Rails is omakase",
"_embedded": {
"comment": [{
"_id": 1,
"comment_title": "FIRST"
}, {
"_id": 2,
"comment_title": "Rails is unagi"
}]
}
}
You could implement a serializer that looks like this to get your payload into shape:
App.PostSerializer = DS.RESTSerializer.extend({
// First, restructure the top-level so it's organized by type
extractSingle: function(store, type, payload, id) {
var comments = payload._embedded.comment;
delete payload._embedded;
payload = { comments: comments, post: payload };
return this._super(store, type, payload, id);
},
normalizeHash: {
// Next, normalize individual comments, which (after extract
)
// are now located under comments
comments: function(hash) {
hash.id = hash._id;
hash.title = hash.comment_title;
delete hash._id;
delete hash.comment_title;
return hash;
}
}
})
When you call super from your own implementation of extractSingle
, the
built-in implementation will find the primary record in your normalized
payload and push the remaining records into the store.
The primary record is the single hash found under post
or the first
element of the posts
array.
The primary record has special meaning when the record is being created
for the first time or updated (createRecord
or updateRecord
). In
particular, it will update the properties of the record that was saved.
Parameters:
-
store
DS.Store -
primaryType
subclass of DS.Model -
payload
Object -
recordId
String
Returns:
the primary response to the original request
extractUpdateRecord
-
store
-
type
-
payload
-
id
-
requestType
extractUpdateRecord
is a hook into the extract method used when
a call is made to DS.Store#update
. By default this method is alias
for extractSave.
Parameters:
-
store
DS.Store -
type
subclass of DS.Model -
payload
Object -
id
String or Number -
requestType
String
Returns:
json The deserialized payload
keyForAttribute
-
attribute
Converts camelCased attributes to underscored when serializing.
Parameters:
-
attribute
String
Returns:
String
keyForRelationship
-
key
-
kind
Underscores relationship names and appends "_id" or "_ids" when serializing relationship keys.
Parameters:
-
key
String -
kind
String
Returns:
String
normalize
-
type
-
hash
-
prop
Add extra step to DS.RESTSerializer.normalize
so links are normalized.
If your payload looks like:
{
"post": {
"id": 1,
"title": "Rails is omakase",
"links": { "flagged_comments": "api/comments/flagged" }
}
}
The normalized version would look like this
{
"post": {
"id": 1,
"title": "Rails is omakase",
"links": { "flaggedComments": "api/comments/flagged" }
}
}
Parameters:
-
type
subclass of DS.Model -
hash
Object -
prop
String
Returns:
Object
normalizeAttributes
()
private
normalizeErrors
()
private
normalizeId
()
private
normalizeLinks
-
data
Convert snake_cased
links to camelCase
Parameters:
-
data
Object
normalizePayload
-
payload
You can use this method to normalize all payloads, regardless of whether they represent single records or an array.
For example, you might want to remove some extraneous data from the payload:
App.ApplicationSerializer = DS.JSONSerializer.extend({
normalizePayload: function(payload) {
delete payload.version;
delete payload.status;
return payload;
}
});
Parameters:
-
payload
Object
Returns:
the normalized payload
normalizeRelationships
()
private
Normalize the polymorphic type from the JSON.
Normalize:
{
id: "1"
minion: { type: "evil_minion", id: "12"}
}
To:
{
id: "1"
minion: { type: "evilMinion", id: "12"}
}
normalizeUsingDeclaredMapping
()
private
pushPayload
-
store
-
payload
This method allows you to push a payload containing top-level collections of records organized per type.
{
"posts": [{
"id": "1",
"title": "Rails is omakase",
"author", "1",
"comments": [ "1" ]
}],
"comments": [{
"id": "1",
"body": "FIRST"
}],
"users": [{
"id": "1",
"name": "@d2h"
}]
}
It will first normalize the payload, so you can use this to push in data streaming in from your server structured the same way that fetches and saves are structured.
Parameters:
-
store
DS.Store -
payload
Object
serialize
-
record
-
options
Called when a record is saved in order to convert the record into JSON.
By default, it creates a JSON object with a key for each attribute and belongsTo relationship.
For example, consider this model:
App.Comment = DS.Model.extend({
title: DS.attr(),
body: DS.attr(),
author: DS.belongsTo('user')
});
The default serialization would create a JSON object like:
{
"title": "Rails is unagi",
"body": "Rails? Omakase? O_O",
"author": 12
}
By default, attributes are passed through as-is, unless
you specified an attribute type (DS.attr('date')
). If
you specify a transform, the JavaScript value will be
serialized when inserted into the JSON hash.
By default, belongs-to relationships are converted into IDs when inserted into the JSON hash.
IDs
serialize
takes an options hash with a single option:
includeId
. If this option is true
, serialize
will,
by default include the ID in the JSON object it builds.
The adapter passes in includeId: true
when serializing
a record for createRecord
, but not for updateRecord
.
Customization
Your server may expect a different JSON format than the built-in serialization format.
In that case, you can implement serialize
yourself and
return a JSON hash of your choosing.
App.PostSerializer = DS.RESTSerializer.extend({
serialize: function(post, options) {
var json = {
POST_TTL: post.get('title'),
POST_BDY: post.get('body'),
POST_CMS: post.get('comments').mapBy('id')
}
if (options.includeId) {
json.POST_ID_ = post.get('id');
}
return json;
}
});
Customizing an App-Wide Serializer
If you want to define a serializer for your entire
application, you'll probably want to use eachAttribute
and eachRelationship
on the record.
App.ApplicationSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
var json = {};
record.eachAttribute(function(name) {
json[serverAttributeName(name)] = record.get(name);
})
record.eachRelationship(function(name, relationship) {
if (relationship.kind === 'hasMany') {
json[serverHasManyName(name)] = record.get(name).mapBy('id');
}
});
if (options.includeId) {
json.ID_ = record.get('id');
}
return json;
}
});
function serverAttributeName(attribute) {
return attribute.underscore().toUpperCase();
}
function serverHasManyName(name) {
return serverAttributeName(name.singularize()) + "_IDS";
}
This serializer will generate JSON that looks like this:
{
"TITLE": "Rails is omakase",
"BODY": "Yep. Omakase.",
"COMMENT_IDS": [ 1, 2, 3 ]
}
Tweaking the Default JSON
If you just want to do some small tweaks on the default JSON, you can call super first and make the tweaks on the returned JSON.
App.PostSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
var json = this._super(record, options);
json.subject = json.title;
delete json.title;
return json;
}
});
Parameters:
-
record
subclass of DS.Model -
options
Object
Returns:
json
serializeAttribute
-
record
-
json
-
key
-
attribute
serializeAttribute
can be used to customize how DS.attr
properties are serialized
For example if you wanted to ensure all your attributes were always
serialized as properties on an attributes
object you could
write:
App.ApplicationSerializer = DS.JSONSerializer.extend({
serializeAttribute: function(record, json, key, attributes) {
json.attributes = json.attributes || {};
this._super(record, json.attributes, key, attributes);
}
});
Parameters:
-
record
DS.Model -
json
Object -
key
String -
attribute
Object
serializeBelongsTo
-
record
-
json
-
relationship
serializeBelongsTo
can be used to customize how DS.belongsTo
properties are serialized.
Example
App.PostSerializer = DS.JSONSerializer.extend({
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
var belongsTo = get(record, key);
key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON();
}
});
Parameters:
-
record
DS.Model -
json
Object -
relationship
Object
serializeHasMany
-
record
-
json
-
relationship
serializeHasMany
can be used to customize how DS.hasMany
properties are serialized.
Example
App.PostSerializer = DS.JSONSerializer.extend({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
if (key === 'comments') {
return;
} else {
this._super.apply(this, arguments);
}
}
});
Parameters:
-
record
DS.Model -
json
Object -
relationship
Object
serializeIntoHash
-
hash
-
type
-
record
-
options
Underscores the JSON root keys when serializing.
Parameters:
-
hash
Object -
type
subclass of DS.Model -
record
DS.Model -
options
Object
serializePolymorphicType
-
record
-
json
-
relationship
Serializes a polymorphic type as a fully capitalized model name.
Parameters:
-
record
DS.Model -
json
Object -
relationship
Object
transformFor
-
attributeType
-
skipAssertion
Parameters:
-
attributeType
String -
skipAssertion
Boolean
Returns:
transform
typeForRoot
-
key
This method is used to convert each JSON root key in the payload
into a typeKey that it can use to look up the appropriate model for
that part of the payload. By default the typeKey for a model is its
name in camelCase, so if your JSON root key is 'fast-car' you would
use typeForRoot to convert it to 'fastCar' so that Ember Data finds
the FastCar
model.
If you diverge from this norm you should also consider changes to store._normalizeTypeKey as well.
For example, your server may return prefixed root keys like so:
{
"response-fast-car": {
"id": "1",
"name": "corvette"
}
}
In order for Ember Data to know that the model corresponding to
the 'response-fast-car' hash is FastCar
(typeKey: 'fastCar'),
you can override typeForRoot to convert 'response-fast-car' to
'fastCar' like so:
App.ApplicationSerializer = DS.RESTSerializer.extend({
typeForRoot: function(root) {
// 'response-fast-car' should become 'fast-car'
var subRoot = root.substring(9);
// _super normalizes 'fast-car' to 'fastCar'
return this._super(subRoot);
}
});
Parameters:
-
key
String
Returns:
the model's typeKey
Properties
attrs
Object
The attrs
object can be used to declare a simple mapping between
property names on DS.Model
records and payload keys in the
serialized JSON object representing the record. An object with the
property key
can also be used to designate the attribute's key on
the response payload.
Example
App.Person = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
occupation: DS.attr('string'),
admin: DS.attr('boolean')
});
App.PersonSerializer = DS.JSONSerializer.extend({
attrs: {
admin: 'is_admin',
occupation: {key: 'career'}
}
});
You can also remove attributes by setting the serialize
key to
false in your mapping object.
Example
App.PersonSerializer = DS.JSONSerializer.extend({
attrs: {
admin: {serialize: false},
occupation: {key: 'career'}
}
});
When serialized:
{
"career": "magician"
}
Note that the admin
is now not included in the payload.
normalizeHash
Object
If you want to do normalizations specific to some part of the payload, you
can specify those under normalizeHash
.
For example, given the following json where the the IDs
under
"comments"
are provided as _id
instead of id
.
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": [ 1, 2 ]
},
"comments": [{
"_id": 1,
"body": "FIRST"
}, {
"_id": 2,
"body": "Rails is unagi"
}]
}
You use normalizeHash
to normalize just the comments:
App.PostSerializer = DS.RESTSerializer.extend({
normalizeHash: {
comments: function(hash) {
hash.id = hash._id;
delete hash._id;
return hash;
}
}
});
The key under normalizeHash
is usually just the original key
that was in the original payload. However, key names will be
impacted by any modifications done in the normalizePayload
method. The DS.RESTSerializer
's default implementation makes no
changes to the payload keys.
Default: undefined
primaryKey
String
The primaryKey is used when serializing and deserializing
data. Ember Data always uses the id
property to store the id of
the record. The external source may not always follow this
convention. In these cases it is useful to override the
primaryKey property to match the primaryKey of your external
store.
Example
App.ApplicationSerializer = DS.JSONSerializer.extend({
primaryKey: '_id'
});
Default: 'id'


FreeBSD's Privacy Policy
Privacy Policy
The FreeBSD Project recognizes that your privacy and the protection of your personal information is important to you and is committed to protecting your privacy. This Privacy Policy describes the measures taken by us to protect your privacy in connection with your use of www.FreeBSD.org (our "Site"). This policy does not cover mirrors of this content on other hosts (the "Mirrors") or other FreeBSD-related services in the FreeBSD.org domain ("Affiliated Sites").
This policy was last modified on October 19, 2012.
PLEASE READ THIS PRIVACY POLICY CAREFULLY.
What information is collected on our Site:
When you visit our Site we gather information regarding your use of our site, such as the pages you view and the time you view each page. We also collect information such as your Internet Protocol ("IP") address, browser type and operating system information. The foregoing information is collectively referred to below as "Your Usage Data."
We store information such as your email address, name, and organization or company ("Personally Identifiable Information") only if you decide to send us such information by submitting a FreeBSD bug or problem report ("PR"), subscribing to one of the FreeBSD mailing lists or posting content to our Site forums. The FreeBSD Project allows unlimited distribution of that content. Information submitted in those reports and lists, including your Personally Identifiable Information, is considered public and will be accessible to anyone on the web. Such information is not only stored on servers belonging to the FreeBSD Foundation, but it is also stored on other servers that mirror the content of our Site. The FreeBSD Foundation has no control over the use of that information, including your Personally Identifiable Information.
This website uses a cookie to record layout style preferences for individual users, as well track anonymous traffic data. A cookie is a small text file provided by a web server that may be placed on your computer by your web browser. Cookies cannot be used to run programs or deliver viruses to your computer. Cookies are uniquely assigned to you, and can only be read by a Web server in the domain that issued the cookie to you. You may refuse the use of cookies by selecting the appropriate settings on your browser, however please note that if you do this you may not be able to use the full functionality of this or other websites.
How the information collected is used:
The FreeBSD Project takes the privacy of our users and members very seriously. We will never sell, rent, or otherwise provide your personally identifiable information to any third parties except as stated in this document. We will not associate any data gathered from your use of our Site with any personally identifying information.
The FreeBSD Project will not use or share your personal information in a manner that differs from what is described in this Privacy Policy without your prior consent.
How your information is protected:
The FreeBSD Project strictly protects the security of the personal information you provide. Personal information we collect is stored in password-controlled servers with limited access, and we carefully protect this information from loss or misuse, and from unauthorized access, disclosure, alteration, or destruction.
When your information may be disclosed:
The FreeBSD Project may disclose personal information if required to do so by law or in the good-faith belief that such action is necessary to:
- conform to the requirements of the law or comply with legal process served on The FreeBSD Project or the Site;
- protect and defend the rights or property of The FreeBSD Project; or
- act in urgent circumstances to protect personal safety of users of The FreeBSD Project, its websites, or the public.
Anonymized aggregate traffic data about website visits may be shared for marketing or research purposes.
The FreeBSD Project uses Google Analytics, a web analytics service provided by Google, Inc. Google Analytics is a tool that enables the FreeBSD Project to see how visitors to our Site use our Site and how they arrive at our Site. Google Analytics uses "cookies", which are text files placed on your computer, to help our Site to analyze visitor activity. We send Google Analytics data that reports on your use of the Site. That data includes Your Usage Data and is not anonymised before it is transmitted to Google Analytics. This un-anonymised data is stored by Google on servers, once it is transmitted to Google. Google will use this information for the purpose of evaluating your use of the Site, compiling reports on website activity for website operators and providing other services relating to website activity and internet usage. Google may also transfer Your Usage Data to third parties where required to do so by law, or where such third parties process the information on Google's behalf. Google will not associate Your Usage Data with any other data held by Google. You may refuse the use of cookies by selecting the appropriate settings on your browser, however please note that if you do this you may not be able to use the full functionality of our Site. By using our Site, you consent to the processing of Your Usage Data by Google in the manner and for the purposes set out above.
With the exception of the transmission of Your Usage Data to Google for the sole purpose of using Google Analytics and the public access we permit to the bug reports you submit, the mailing lists you subscribe to, and posting content to our Site forums, the FreeBSD Project does not share un-anonymised data with anyone.
Updates to this privacy policy:
We may occasionally update this Privacy Policy. When we do, we will also revise the "last modified" date at the top of the Privacy Policy. We encourage you to periodically review this Privacy Policy to stay informed about how we are protecting the personal information we collect. Your continued use of the service constitutes your agreement to this Privacy Policy and any updates.
Sale of assets:
In the unlikely event that The FreeBSD Project or substantially all of its assets are acquired, your information may be one of the transferred assets.
Third party links:
In an attempt to provide you with increased value, we may include third party links on our site. These linked sites have separate and independent privacy policies. This Privacy Policy only covers our Site and does not cover any other website. We therefore have no responsibility or liability for the content and activities of these linked sites. Nonetheless, we seek to protect the integrity of our site and welcome any feedback about these linked sites (including if a specific link does not work).
Contacting Us:
We would like to receive your comments and questions about this Privacy Policy and any other matter you have regarding our Site. Please address comments or questions to us via e-mail at: privacy@FreeBSD.org.


So now you know all there is to know about transducers: what they are, why they're good, how to use them. But what if the transducers supplied by xduce aren't enough?  The answer is to write your own.
The first important thing to understand is what is possible with transducers, at least in this library. There are some operations that you might take for granted that it turns out aren't possible to render as a transducer, precisely because transducers are so generalized. Let's look at some of them.
Transducers are one-to-one. They take one collection and go through it one step at a time, returning one collection. Therefore, operations like zip
(which takes multiple collections and turns them into one) and partition
(which takes one collection and turns it into several) are not possible.
I am not convinced that it has to be this way. My future plans include investigating this to see if there's a way around this restriction, particularly for multiple inputs (multiple outputs may well just be infeasible with this mechanism). For the moment though, it's one collection in, one collection out.
Transducers need to cover every type of collection that could be used with them, and that's where there gets to be a problem with operations that need to know where the end of the collection is. How can you take the last three elements from an infinite iterator? What is the first element in a stream if it's reversed? While these operations make sense for things like arrays and strings, they can't be written to work with other kinds of collections.
I spent a lot of time working with this while writing chunk
. I was looking for a way to, for instance, create an iterator whose elements were other iterators. There was one technical hurdle after another though, and I eventually settled on declaring that the chunks themselves were going to be arrays, even if those arrays were elements of an iterator.
I'm also not convinced that this can't be overcome. I didn't run into anything that made me believe that it would be impossible to have chunks that were arbitrary collections. It's another thing that will be revisited, but for now, your chunks are arrays.
map
Now that we have the bad news out of the way, let's look at how a few of the existing transducers are written. We'll start with the most basic of them all, map
.
It's been mentioned before that a transducer in xduce is a function that returns a reducer object, so to write our own, we will have to start with writing that reducer object. The object has to have three function properties, all named with the reduction protocol names. These functions chain to the functions on the reducer object of the next transducer, all the way to the last object which is the actual reducer that creates the final output.
For most transducers, the xduce.protocols.init
and xduce.protocols.result
functions just chain to the next one. This is because in most cases, the reducer is responsible for both of these operations. The transducer has nothing to do with them, so it just calls the same function in the next transducer.
It's the xduce.protocols.step
function that's the important one. This is the actual reducer function where each transducer does its work. With that in mind, let's look at the code for the map
transducer.
// In this code, `p` is an alias for `xduce.protocols`
function map(fn) {
return xform => {
return {
[p.init]() {
return xform[p.init]();
},
[p.step](acc, input) {
return xform[p.step](acc, fn(input));
},
[p.result](value) {
return xform[p.result](value);
}
};
}
}
It's plain to see that the p.init
and p.result
functions are simply calling the corresponding functions on the next transducer in the chain (named xform
). The p.step
function is doing something slightly different though: it's calling the corresponding function on the next transducer, but it's changing the input by running it through its mapping function first. This step function is run one time for each input from the prior transducer, meaning that each value is passed through the mapping function one at a time and sent on to the next transducer.
You may recall from the theory section though that in xduce, a transducer is a function that produces a function that takes the next transducer in the chain and returns a reducer object. That's what the outer map
function does — it returns exactly such a function. Library functions like sequence
expect to receive exactly what this map
function is returning, and the right thing will happen.
There are reasons why a transducer may want to fail to pass a value. A filter
transducer will drop elements that don't pass its predicate function, a take
transducer wants to skip all of the element after a certain index, and so on. This is accomplished easily enough by just not delegating to the next step
function and returning the unmodified accumulator instead.
Here's the step
function for the filter
transducer, for illustration.
[p.step](acc, input) {
return fn(input) ? xform[p.step](acc, input) : acc;
}
Again this is simple in operation, though in a different way. The input value is not modified in this transducer. Instead, it's passed into the predicate fn
, and if that returns true
, it's just passed along to the next transducer's step
function. If it returns false
though, the input it simply ignored. The acculmator itself is returned without passing it to the next transducer. This is because since there's not anything being added to the accumulator — the input value is being ignored — there's no need to run any of the other step
functions. This accumulator will simply make it to the end of the chain, where it'll be used as the accumulator for the next input element.
take
is a little trickier, at least to make work properly. In theory, a take
transducer can just keep track of how many elements it's processed, and when it reaches a certain number, do exactly the same thing that this filter
transducer does (pass along the accumulator without doing anything). This is not a good way to do it though, for two reasons.
First of all, it's inefficient. While this transducer stops processing on an element, any transducers that came before it have already done their processing. This is reasonable in filter
, where it's presumed that a lot of the elements will get passed into the final collection. But for take
, this may not be true. Running take(3)
on a million-item collection would mean that 999,997 items are being processed unnecessarily since they're going to be dropped anyway.
Secondly, one of the nice things about the real take
transducer is that it can turn infinite collections into finite ones. It could not do this if it continued to process every element, even if that processing was simply to drop the element. There has to be a way to stop the reduction, not simply drop elements one at a time.
How does take
do this? Let's have a look at its code.
function take(n) {
return xform => {
let i = 0;
return {
[p.init]() {
return xform[p.init]();
},
[p.step](acc, input) {
let result = acc;
if (i < n) {
result = xform[p.step](acc, input);
if (i === n - 1) {
result = ensureCompleted(result);
}
}
i++;
return result;
},
[p.result](value) {
return xform[p.result](value);
}
};
}
}
Again, p.init
and p.result
just chain to the next transducer, as we've seen before. p.step
, however, does something very different. If the limit has not yet been reached, it just chains to the next transducer. But if the limit has been reached, it passes that chained result to the ensureCompleted
function.
ensureCompleted
marks the result in a way that the engine will know that the transduction process is finished at that point. It will take the result as the final result, pass it through p.result
, and return it. Even if there are an infinite number of items remaining in the collection, its job is done and it returns. There is nothing further for the transducer writer to do — once a result is returned from p.step
that has been returned from ensureCompleted
, processing will cease automatically.
There are a number of these status-related functions: complete
, uncomplete
, isCompleted
, ensureCompleted
, and ensureUncompleted
. Information about them is available in the API documentation.
These are three of the most basic transducers. You may want to have a look at the code for transducers like repeat
and chunk
to see some of the other things that can be done. As long as you work within the constraints mentioned earlier on this page, there's very little limit to what a transducer can do.


?Comocladia
mollifolia
Ekm.
&
Helw.
Determined
by
Hilary
M.
Davis
Missouri
Botanical
Garden
17
January
2002
EOT
G
A
R
O
THE
NEW
YORK
BOTANICAL
GARDEN
EXPLORATION
OF
CUBA
SIEBRA
CUBITAS,
CAMAGLCEY
NO
NEW
YORK
BOTANICAL
GARDEN
0
49880
$r)
J.
A.
SHAFER,
COLLECTOR
FEBRUARY
19-21,
1909
01498801
Legend -
Level of confidence that token is an accurately-transcribed word
extremely low
very low
low
undetermined
medium
high
very high


For Ace's demo pages I've used Handlebars templating library which makes it easy to separate layouts and large pages into smaller sections.
They are located inside views
folder .
The compiled HTML files are inside html
folder.
views/layouts folder contains layouts and their relevant partials (sections):
#index.hbs
inside the layout's folder
-
dashboard
folder is the layout used in default Dashboard page -
starter
is the layout for Starter page. It's similar to main dashboard layout but with minimal content to make it easy to figure out the structure and use it -
dashboard-2
is used for Dashboard 2 page and has different layout. In this layout sidebar starts from top of page and navbar is inside content area -
dashboard-3
is used for Dashboard 3 page and is similar to Dashboard 2 it has different initial sidebar and navbar themes and content -
dashboard-4
is used for Dashboard 4 page and is similar to main Dashboard layout except it has different initial sidebar and navbar themes and content -
horizontal
is the layout for Horizontal Menu page -
two-menus-1
is the layout for Two Menus page -
landing-1
is the layout for Landing Page 1 page -
landing-2
is the layout for Landing Page 2 page which is similar to Lading Page 1 except for different navbar content -
coming-soon
is the layout for Coming Soon page -
login
is used for the Login page -
_commons
folder contains some partials that are common between above layouts such as settings box, html head, etc ...
views/pages folder contains demo pages along with their partials
which are different sections of a page.
#index.hbs
inside the page's folder
For example for Profile page:
views/pages/
page-profile/#index.hbs
is the main<EMAIL_ADDRESS>contains the script for this page, for example to initiate circular charts<EMAIL_ADDRESS>contains the CSS used only in this page. They are not included in the mainace.css
because they are not needed throughout Ace and are only used in this<EMAIL_ADDRESS>contains the list of third party files and plugins used in this pageviews/pages/page-profile/profile-left.hbs
contains the left column of profile page (The user info)views/pages/page-profile/activity.hbs
contains the content of the second tab (Activity tab)- etc
views/data
folder contains data used in Ace's demo such as list of pages, navbar notifications, dashboard's stats, etc.
They are in json or csv format.
For example:
views/data/layouts/sidebar.json
contains the page list (i.e. sidebar elements)views/data/layouts/navbar-notif.json
contains navbar notification itemsviews/data/pages/dashboard/conversations.json
contains the conversation items displayed in dashboard
In Ace's demo pages, data is retrieved using a custom handlebars helper
{{#each (get "DATA_NAME")}}
{{#each (get "conversations")}}

{{message}}

{{/each}}
{{#each (get "layout/navbar-notif")}}

{{title}}

{{/each}}
assets
folder contains the following:
- image which contains some images and avatars for demo
- fonts which contains Open Sans font files used by default in Ace's demo. However you can should fonts directly from Google fonts.
- Local fonts are used for faster loading of pages during local development. See Fonts & Icons


|
SimpleITK
1.1.0
|
A class used to instantiate and generate function object to templated member functions. More...
#include <sitkMemberFunctionFactory.h>
Public Types | |
| typedef Superclass::FunctionObjectType | FunctionObjectType |
| typedef TMemberFunctionPointer | MemberFunctionType |
| typedef ::detail::FunctionTraits< MemberFunctionType >::ClassType | ObjectType |
| typedef MemberFunctionFactory | Self |
| typedef MemberFunctionFactoryBase< TMemberFunctionPointer, int > | Superclass |
Public Member Functions | |
| FunctionObjectType | GetMemberFunction (PixelIDValueType pixelID, unsigned int imageDimension) |
| Returns a function object for the PixelIndex, and image dimension. More... | |
| bool | HasMemberFunction (PixelIDValueType pixelID, unsigned int imageDimension) const SITK_NOEXCEPT |
| Query to determine if an member function has been registered for pixelID and imageDimension. More... | |
| MemberFunctionFactory (ObjectType *pObject) | |
| Constructor which permanently binds the constructed object to pObject. More... | |
| template<typename TImageType > | |
| void | Register (MemberFunctionType pfunc, TImageType *) |
| Registers a specific member function. More... | |
| template<typename TPixelIDTypeList , unsigned int VImageDimension, typename TAddressor > | |
| void | RegisterMemberFunctions (void) |
| Registers all member functions in TPixelIDTypeList and simple::InstantiatedPixelIDTypeList over itk::Image<Pixel, ImageDimension> More... | |
| template<typename TPixelIDTypeList , unsigned int VImageDimension> | |
| void | RegisterMemberFunctions (void) |
| Registers all member functions in TPixelIDTypeList and simple::InstantiatedPixelIDTypeList over itk::Image<Pixel, ImageDimension> More... | |
Protected Attributes | |
| ObjectType * | m_ObjectPointer |
A class used to instantiate and generate function object to templated member functions.
| TMemberFunctionPointer | is the type of pointer to member function |
Example member function pointer:
The RegisterMemberFunctions instantiate the templeted member functions and registers the member function pointer, so that it be used for dispatch later. Later they can be retrieve with the GetMemberFunction methods, which return a function object with the same arguments as the templated member function pointer.
An instance of a MemberFunctionFactory is bound to a specific instance of an object, so that the returned function object does not need to have the calling object specified.
Definition at line 55 of file sitkMemberFunctionFactory.h.
| typedef Superclass::FunctionObjectType itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::FunctionObjectType |
Definition at line 66 of file sitkMemberFunctionFactory.h.
| typedef TMemberFunctionPointer itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::MemberFunctionType |
Definition at line 64 of file sitkMemberFunctionFactory.h.
| typedef ::detail::FunctionTraits<MemberFunctionType>::ClassType itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::ObjectType |
Definition at line 65 of file sitkMemberFunctionFactory.h.
| typedef MemberFunctionFactory itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::Self |
Definition at line 62 of file sitkMemberFunctionFactory.h.
| typedef MemberFunctionFactoryBase<TMemberFunctionPointer, int> itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::Superclass |
Definition at line 61 of file sitkMemberFunctionFactory.h.
| itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::MemberFunctionFactory | ( | ObjectType * | pObject | ) |
Constructor which permanently binds the constructed object to pObject.
| FunctionObjectType itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::GetMemberFunction | ( | PixelIDValueType | pixelID, |
| unsigned int | imageDimension | ||
| ) |
Returns a function object for the PixelIndex, and image dimension.
pixelID is the value of Image::GetPixelIDValue(), or PixelIDToPixelIDValue<PixelIDType>::Result
imageDimension is the value returned by Image::GetDimension()
Example usage:
If the requested member function is not registered then an exception is generated. The returned function object is guaranteed to be valid.
| bool itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::HasMemberFunction | ( | PixelIDValueType | pixelID, |
| unsigned int | imageDimension | ||
| ) | const |
Query to determine if an member function has been registered for pixelID and imageDimension.
| void itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::Register | ( | MemberFunctionType | pfunc, |
| TImageType * | |||
| ) |
Registers a specific member function.
Registers a member function which will be dispatched to the TImageType type
| void itk::simple::detail::MemberFunctionFactory< TMemberFunctionPointer >::RegisterMemberFunctions | ( | void | ) |
Registers all member functions in TPixelIDTypeList and simple::InstantiatedPixelIDTypeList over itk::Image<Pixel, ImageDimension>
| TAddressor | is the type of a class who as a templated operator() which returns TMemberFunctionPointer, this will default to detail::MemberFunctionAddressor if not specified |
Example usage:
Example usage:
|
inline |
Registers all member functions in TPixelIDTypeList and simple::InstantiatedPixelIDTypeList over itk::Image<Pixel, ImageDimension>
| TAddressor | is the type of a class who as a templated operator() which returns TMemberFunctionPointer, this will default to detail::MemberFunctionAddressor if not specified |
Example usage:
Example usage:
Definition at line 120 of file sitkMemberFunctionFactory.h.
|
protected |
Definition at line 155 of file sitkMemberFunctionFactory.h.


{% extends "app-resolvable-errors.html" %} {% block content %} Yo, there's nothin' there, son. {% endblock content %}


9
Judgment on Israel's Enemies
1 The burden of the word of the
LORD is against the land of Hadrach
and Damascus is its resting place.
For the LORD has an eye on mankind
and on all the tribes of Israel,
2 and on Hamath also, which borders on it,
Tyre and Sidon, though they are very wise.
3 Tyre has built herself a rampart
and heaped up silver like dust,
and fine gold like the mud of the streets.
4 But behold, the Lord will strip her of her possessions
and strike down her power on the sea,
and she shall be devoured by fire.
5 Ashkelon shall see it, and be afraid;
Gaza too, and shall writhe in anguish;
Ekron also, because its hopes are confounded.
The king shall perish from Gaza;
Ashkelon shall be uninhabited;
6 a mixed people shall dwell in Ashdod,
and I will cut off the pride of Philistia.
7 I will take away its blood from its mouth,
and its abominations from between its teeth;
it too shall be a remnant for our God;
it shall be like a clan in Judah,
and Ekron shall be like the Jebusites.
8 Then I will encamp at my house as a guard,
so that none shall march to and fro;
no oppressor shall again march over them,
for now I see with my own eyes.
The Coming King of Zion
9 Rejoice greatly, O daughter of Zion!
Shout aloud, O daughter of Jerusalem!
behold, your king is coming to you;
righteous and having salvation is he,
humble and mounted on a donkey,
on a colt, the foal of a donkey.
10 I will cut off the chariot from Ephraim
and the war horse from Jerusalem;
and the battle bow shall be cut off,
and he shall speak peace to the nations;
his rule shall be from sea to sea,
and from the River to the ends of the earth.
11 As for you also, because of the blood of my covenant with you,
I will set your prisoners free from the waterless pit.
12 Return to your stronghold, O prisoners of hope;
today I declare that I will restore to you double.
13 For I have bent Judah as my bow;
I have made Ephraim its arrow.

The LORD Will Save His People
14 Then the LORD will appear over them,
and his arrow will go forth like lightning;
the Lord GOD will sound the trumpet
and will march forth in the whirlwinds of the south.
15 The LORD of hosts will protect them,
and they shall devour, and tread down the sling stones,
and they shall drink and roar as if drunk with wine,
and be full like a bowl,
drenched like the corners of the altar.
16 On that day the LORD their God will save them,
as the flock of his people;
for like the jewels of a crown
they shall shine on his land.
17 For how great is his goodness, and how great his beauty!
Grain shall make the young men flourish,
and new wine the young women.


The Ant-Contrib project is a collection of tasks (and at one point maybe types and other tools) for Apache Ant.
This Software is distributed under the Apache Software License.
First you must install Apache Ant itself, most of the Ant-Contrib tasks require Ant 1.5 or higher to work properly. You can download Ant from Apache.
Then you need the Ant-Contrib tasks themselves. As there is no
release of these tasks yet, you have to build them from sources.
Fortunately this is easy, check out the sources (grab the
ant-contrib
module from CVS), change
into the source directory of ant-contrib and type
ant
. After Ant has completed, you'll find
ant-contrib-0.4.jar
in the lib
subdirectory.
You now have the choice:
ant-contrib-0.4.jar
to the
lib
directory of your Ant installation. If you
want to use one of the tasks in your own project, add the line
<taskdef resource="net/sf/antcontrib/antcontrib.properties"/>to your build file.
ant-contrib-0.4.jar
in a separate
location. You now have to tell Ant explicitly where to find it
(say in /usr/share/java/lib
):
<taskdef resource="net/sf/antcontrib/antcontrib.properties"> <classpath> <pathelement location="/usr/share/java/lib/ant-contrib-0.4.jar"/> </classpath> </taskdef>
Copyright © 2002-2003 Ant-Contrib Project. All rights Reserved.


HP's Black Friday ad has discounted laptops, desktops, Chromebooks, & more
We're only just into November, but we're already seeing a bunch of Black Friday ads popping up online as we get nearer the year's biggest shopping season. HP is the latest retailer to have its ad revealed which means we have time to take a look and assess whether HP should be a destination for you this Black Friday.
Deals worth considering
As you might expect, HP's ad is chock-full of deals on laptop and desktop PCs making it worth a look if you're in the market for a computer upgrade. There are also Chromebooks, monitors, and other related accessories with some sweet savings. Of course, not all deals on Black Friday are worth jumping on, so we've outlined a few choice picks you should consider as well as a some to avoid.
HP Laptop 15t
Source: HP
One of HP's Doorbuster deals makes for an enticing offer. This laptop sells for as much as $1,350 and will be going for $499.99 during the Black Friday sale and only $480 for the first $480 for the first customers to buy it at midnight PST on November 29. It packs in a 10th-gen Intel Core i7 processor, 12GB RAM, and a 256GB SSD.
HP Spectre x360
Source: HP
This well-reviewed machine drops to only $800 on Novemebr 29 at 4 p.m. PST. The convertible laptop is equipped with an 8th-gen Intel Core i5 processor and 13.3-inch HD touchscreen.
HP Chromebook 11
Source: HP
If you're wanting to use Black Friday to grab a Chromebook, the already-affordable HP Chromebook 11 is worth a look at $149.99 during the sale for a savings of $50. The super portable machine has a over 12 hours of battery life and runs Chrome OS for a simple and secure computing experience.
HP 27er Monitor
Source: HP
Add some extra screen real estate to your desk with this 27-inch HP monitor. It falls to just $160 for the first customers buying it on Black Friday, saving $90 off the retail price. the 23-inch version of this monitor will also be on sale for just $110.
Other deals to keep an eye on:
- HP Laptop 15z - $250 (was $520)
- OMEN 875-1045m Obelisk Desktop PC - $799.99 (was $1,100)
- Extra 5% off select gaming PCs over $1000 with code HOLIDAYGAMER
Deals worth ignoring
As with all Black Friday sales, many of the deals feature are not really deals at all. HP's ad is no different with a few less-than-appealing options in there. Some of these products may see are being advertized at prices we see throughout the year and we expect they may drop lower at other retailers during Black Friday and Cyber Monday. Keep your eyes peeled for even better discounts.
- HP Sprocket Studio - $149.99
- HP Chromebook 14 - $229.99
- Bose Home Speaker 500 - $299
Store information
Get the latest Black Friday deals delivered straight to your inbox!
HP's sale officially kicks off on Thanksgiving (November 28), though HP doesn't state a start time. A few of the listed deals are already showing at HP's site, so if you see something you like at the price in the ad it may be worth picking it up already.
A number of the deals are featured as Doorbusters and have set kick off times. These prices, similar to Amazon Lightning deals, will be limited in both supply and time so you want to check those out in advance so you can be ready to buy when their time comes.
Shipping is free and select accessories and bundled purchases give you extra discounts, so be sure to have a keep an eye out for those.
 If not, don't worry. We will help you out every step of the way. Be sure to sign up for our Black Friday newsletter, and keep your eye here for more ads and leaks as they are made available.
Get ready for Black Friday
- Black Friday Deals
- Black Friday: Everything You Need To Know
- Amazon Black Friday 2019: Best Deals, Ads, & Sales
- Best Buy Black Friday 2019: Best Deals, Ads, & Sales
- Walmart Black Friday 2019: Best Deals, Ads, & Sales
- Black Friday Carrier Deals: Verizon, Sprint, T-Mobile, Mint Mobile, & more
- Black Friday VPN Deals: ExpressVPN, PIA, NordVPN, & more
We may earn a commission for purchases using our links. Learn more.


Data-driven analyst with a history of developing tools and systems that streamline decision-making. Highly fluent in analytical tools including SQL and Excel and complex programming and database languages including JavaScript and Python. Over 6 years of finance and data analysis experience with a focus on creating tools and models that drive business decision making.