Saturday, March 23, 2013

Git Cheat Sheet


Your Best Reference Pro Git -- download for PDF, ePub or Mobi formats here http://git-scm.com/book

Tip: Use Calibre's built-in server to push the ePub to your smartphone.


----------------------------------------------------------------------
Basic Git - Startup
----------------------------------------------------------------------


Basic Git Architecture
  • Decentralized - everybody's copy of a project is a full database copy
  • versions not stored as deltas - instead they are stored as complete instances of the files
  • all content is compressed in the database
Basic Git Lifecycle
  • new file => Untracked --> files unknown to Git
  • 'git add' => Staged --> ready for commit, file or version not yet in database
  • 'get commit' => Tracked --> file/version tracked in the database
Even Tracked files, once modified, require Staging again! i.e. you must 'git add' again!!!

Configuring Your Global Identity - Do this when first installing Git.

Set name
> git config --global user.name "Your Name"
Set email address
> git config --global user.email you@example.com

Create New Project
Create source directory. Create as a Git repo via:
> git init

Unlike SVN/CVS/VSS, the repo lives in your project directory. It's OK.
Ignore Files with .gitignore
  • Put .gitignore at the project top level directory
  • Add a line per filter; example:
# this is a comment
     *.class
     logs/
  • this example excludes all class files, and the logs directory

----------------------------------------------------------------------
Basic Git - Typical Workflow
----------------------------------------------------------------------


See what has Changed
> git status

Commit Tracked, Modified Files
> git add -u
> git commit -m "notes about the commit"
Show changes to tracked files that are NOT staged
> git diff
Show changes to tracked files that ARE staged for commit
> git diff --staged (or --cached, an alias)

Add an Untracked File
> git add <filename>

Revert changes to a tracked file:
> git checkout -- <filename>

Revert a Staged File
It got added to stage area, but you don't want to commit it yet
> git reset HEAD <filename>

Revert all Staged Files
Unstages all from the staging area - maybe you ran 'git add .' and .gitignore did not filter properly
> git reset HEAD

Temporarily Switch to Another Branch
Got some changes not ready for committing, but need to pop to another branch - stash the current work
> git stash

Switch to another branch (git checkout)
Then come back and reapply latest stashed changes
> git stash apply

Or see list of stashes
> git stash list
And apply a named stash
> get stash apply stash@{1}

Pull Updates from a Remote Repo
Typical - when you're working with a team
- the rebase keeps the history cleaner by moving your local commits to after the new merges
> git pull --rebase

----------------------------------------------------------------------
Basic Git - Details
----------------------------------------------------------------------


Add a New File
create a README.md - this stages it
> git add README.md
and commit
> git commit -m "initial" README.md
Commit All Changed Files
Stage any changed files
> git add .
See what is staged
> git status
Commit
> git commit -m "your message"
Do all at once
> git add -A && git commit -m "your message"
Or use -a to skip staging (the 'git add' part)
> git -a -m "message"
Revert a Staged File
It got added to stage area, but you don't want to commit it yet
> git reset HEAD <filename>
Revert all Staged Files
Unstages all from the staging area
> git reset HEAD
Revert all Changes on the Branch
Don't like where the branch is going? Undo all changed files
> git checkout -f
Revert changes to a tracked file:
> git checkout -- <filename>
Revert a commit
There are myriad solutions, see here
http://stackoverflow.com/questions/927358/how-to-undo-the-last-git-commit
See What is Modified
Show any modified or untracked files
> git status
Show changes to tracked files that are NOT staged
> git diff
Show changes to tracked files that ARE staged for commit
> git diff --staged (or --cached, an alias)
Committing a File
Check for annoying whitespace changes
> git diff --check
See what changed - shows patch diffs
> git log -p filename
Commit and enter comment editor
> git commit README.md Conventional commit comment:
  • 50-char summary, followed by...
  • blank line, followed by...
  • detailed description of change
View Commit Histories
See all changes, ever
> git log
And for a certain file
> git log -2 filename
Just the last 2 commits
> git log -2
Last commit with patch diffs
> git log -p -1
Commits since a certain date
> git log --since 1.week
> git log --since 10.days
> git log --since 2013-02-03
Commits in date range
> git log --since 2.weeks --until 1.week
Commits by committer (modifier of file)
> git log --committer username
See 'gitk' for a visual git log UI
Changing Last Commit
Add some files to the last commit. Adds whatever is staged:
> git commit --amend
Change the commit comments - assumes nothing is staged:
> git commit --amend -m "new message"
Deleting Committed Files
Just a single file, locally and from repo
> git rm filename.txt
A directory of files, locally and from repo
> git -r rm dirName
A directory of files, but ONLY from the repo, not local copies
> git -r --cached rm dirName
A file that was already staged:
> git -f filename.txt
Requires commit afterwards.
Renaming a File
Not explicitly supported internally, but calculated; and a convenience function:
> git mv oldname.txt newname.txt
Requires commit afterwards.
----------------------------------------------------------------------
Tagging
----------------------------------------------------------------------

Listing existing Tags
> git tag
Using wildcards to find tags
> git tag -l 'v1.2*'

Using a Tag
> git checkout

Creating a lightweight Tag
Example for v1.0; lightweight tags are just pointer sets
> git tag v1.0

Creating an Annotated Tag
These are checksummed and contain annotation, and optional signature
> git tag -a v1.0 -m 'release 1.0'

Creating a Signed Tag
Must have a private key installed
> git tag -s v1.0 -m 'release 1.0'

Tagging After the Fact
Forgot to tag? No matter, find the relevant commit
> git log --pretty=oneline
And tag using the checksum (first 6 or 7 characters of the checksum)
> git tag -a v1.2 9fceb02
And verify
> git show v1.2

Sharing Tags with Remotes
Tags must be pushed out like branches are
> git push origin v1.5

Or, all at once
> git push origin --tags

----------------------------------------------------------------------
Branching and Merging
----------------------------------------------------------------------

Show Current Branch
Currently checked-out branch
> git branch

List All Existing Branches
Show all branches - star is currently checked out
> git branch -v (verbose gives info on last commit)

Creating a Branch
Create new branch based on some other branch
> git checkout -b new-branch existing-branch

Create new local branch from current, and immediately check it out
> git checkout -b newbranch

Create new local branch from the remote master branch, and immediately check it out
> git checkout -b newbranch origin/master

Merge one local branch into another
This merges feature into master
> git checkout master
> git merge feature

Merge branch from remote repo into local repo
First update local copy of remote
> git fetch origin
Look at changes to remote branch
> git log origin/featureA ^featureA (not sure what the ^ is)
Merge into local branch (checkout first if necessary)
> git merge origin/featureA

Deleting a Branch
Must not be your current branch, and must not have outstanding changes
> git branch -d >branch<

----------------------------------------------------------------------
Creating a Repository on GitHub
----------------------------------------------------------------------


You created a project and want to post it to share with colleagues.
Locally you did this:
    ir="ltr">create local Git repo
> git init
  • Do work
  • Commit files
On GitHub you do this
  • Create Repo (there is a magic button)
  • Note the new project URL (ending in .git)
Then locally, do this:
> git remote add origin <new git URL, ends in .git>
> git remote -v (examine your remotes)
> git push -u origin master (or whatever branch name you're working in


----------------------------------------------------------------------
Working with Remote Repositories
----------------------------------------------------------------------

A Remote is an alias to a remote repository.

Show List of Remotes
See list of known remotes
> git remote -v

Add alias to a remote repo - alias is often 'origin' by convention
> git remote add <alias> [root project URL ending in .git]

Uploading to a Remote
Upload branch contents to a named remote (origin)

Uploading a Branch to Someone Else's Repo
When branch name is the same
> git -u push <alias> <branch-name>
...example:
> git push -u origin master

When branch name is different on remote
> git -u push <alias> <local-branch-name>:<remote-branch-name>
...example:
> git -u push origin featureX:patch331

The -u option sets up a upstream branch - i.e. maps local to remote branch.

Getting changes from Remote Repo
Doing this will do a 'fetch' followed by a 'merge' - i.e. get you up to date
Pull will always merge into the current branch; specify which branch to pull from:
> git pull

Fetch is less damaging - not sure yet how to use effectively
> get fetch


----------------------------------------------------------------------
Working with GitHub Repos
----------------------------------------------------------------------

There's a project you want to get some changes into. Do this.

1. Look at the project's GitHub page; see 'Network' and 'Issues' tags.
Make sure someone else isn't already doing what you wanted to do.
2. On the GitHub page, press 'Fork' to create your own repo.
3. Clone the fork locally using its new URL
> git clone [url of my fork] <my local dir>
5. Add the fork Repo as a remote
> git remote add origin <fork URL ending in .git>
6. Add the orginal Repo as a remote for easy updating (to stay sync'd)
> git remote add upstream <original project URL ending in .git>

Contributing to Projects - Go with the Flow
Different projects may have different workflows - find out by reading the project README.
This can vary based on project size and organizer preference.

Simple Workflow Example
A simple contribution workflow looks like this:
  • developer forks project
  • clones fork locally ( steps 1-6 above)
  • does work in topic branches - not master!
  • pushes topic branches up to fork repo
  • submits pull request to original project via GitHub
The Steps
1. Do initial Fork setup, steps 1-6 above

2. Create a Topic Branch
This is a branch for doing local work in
> git checkout -b new-feature origin/master

3. Keep Local work in synch
Synch with the original project - first get all its branches and updates
> git fetch upstream
Merge its change into your working branch - where 'master' is the remote branch to merge in
> git merge upstream/master

4. Do work
Do work in the topic branch ('new-feature' above) as usual
Stage and Commit when ready
Periodically merge in remote changes (#3)

5. Push changes out to your Fork repo
> git push origin new-feature

6. When Ready
Good tests are included? Bugs are out?
Log into your Github fork project and switch branches (using selector) to your new-feature branch
Verify contents; update Readme file
Navigate to original project and submit a Pull Request

Tuesday, March 12, 2013

Grails bootstrap environment

Every now and then we'd really like to know what Grails has mystically configured for us in that strange little world of the Bootstrap.

The following is a chunk you can paste into Bootstrap.groovy to see what is under the hood and available to you.

One of the more useful purposes I have found for this information is pulling out service classes for some sort of startup initialization.


        println "------- ServletContext Attributes -----------------------"
        def names = servletContext.getAttributeNames()
        names.each() {println "$it"}
 
        def ctx = servletContext.getAttribute('org.codehaus.groovy.grails.APPLICATION_CONTEXT')
        def osiv
        if (ctx) {
            def beans = ctx.beanDefinitionNames
            beans.sort()
            println "------- AppContext Beans -----------------------"
            beans.each() {
                if (it.indexOf("Interceptor") > 0 || it.indexOf('interceptor') > 0 || it.indexOf('Handler') > 0) {
                   println "$it"
                }
                if (it == 'openSessionInViewInterceptor') {
                    // Get the interceptor, check it state
                    osiv = ctx.getBean(it)
                    println "\t\t--> OSIV enabled?? ${osiv.isSingleSession()}"
                }
           }
 
           // Get the private interceptors field from the request handlerMapping class
           def field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField('interceptors')
           field.accessible = true
           println "------- Interceptors via controllerHandlerMappings -----------------------"
           // Get this Field on the given object, the actual HandlerMapping that declares the interceptors
           def interceptors = field.get(ctx.controllerHandlerMappings)
           if (interceptors) {
               println "Got interceptors class: ${interceptors.class.name}"
               interceptors.each() {
                   println "$it"
               }
           }
           else {
               println "Could not get interceptors class"
           }
        }
        else {
            println "No AppContext"
        }
        
        def app = servletContext.getAttribute('grailsApplication')
        def messageManagerService
        
        if (app) {
            println "\n-------------------------------------------------"
            println "------- grailsApplication -----------------------"
            println "-------------------------------------------------"
            println "\n------- Properties -----------------------"
            app.properties.each { key ->
                println "### $key"
            }
            println "\n------- All Artefact Classes -----------------------"
            def cz = app.allArtefactClasses
            cz.each {
                println it
            }
            println "\n------- Domain Classes -----------------------"
            cz = app.getArtefacts(DomainClassArtefactHandler.TYPE)
            cz = app.domainClasses
            cz.each {
                println "$it (${it.propertyName})"
            }
            println "\n------- Controller Classes -----------------------"
            //cz = app.getArtefacts(ControllerArtefactHandler.TYPE)
            cz = app.controllerClasses
            cz.each {
                println "$it (${it.propertyName})"
            }
            println "\n------- Service Classes -----------------------"
            //cz = app.getArtefacts(ServiceArtefactHandler.TYPE)
            cz = app.serviceClasses
            cz.each {
                println "$it (${it.propertyName})"
            }
            println "\n------- UrlMappings Classes -----------------------"
            //cz = app.getArtefacts(UrlMappingsArtefactHandler.TYPE)
            cz = app.urlMappingsClasses
            cz.each {
                println "$it (${it.propertyName})"
            }
            
            // Pull out my service class by registered bean name
            messageManagerService = app.mainContext['messageManagerService']
        }
        else {
          println "No grailsApplication"  
        }
        



Wednesday, March 6, 2013

eBook Cheat Sheet

Just some of my Tomboy notes about publishing eBooks.

Ebook Formats
  • ePub - this is an open standard - meaning nobody owns it
    • latest version is ePub3 which supports media overlays and audio/video
    • supported by Nook, Kobo, and reader apps like Stanza
  • mobi - this is Amazon's format for the Kindle
    • this is replaced by KF8 for the newer devices
      • supports more multimedia options
      • roughly comparable to ePub2
      • not as robust as ePub3
    • why is my MOBI much bigger than my ePub?????
      • it's because MOBI aggregates alternate formats into this one file to account for different devices
      • this is OK - the size of your MOBI file will NOT be the size of the download - that will depend on what kind of device it's going to
Linux ePub Viewers
  • Calibre
    apt-get install calibre
Windows ePub Viewers
  • Calibre
  • Adobe Digital Editions
  • Amazon Kindle Previewer
Creating a MOBI from an ePub
  • Use Amazon's free KindleGen app
Getting an eBook onto a Device
  • Using Calibre
    • supports ePubs
    • install Calibre
    • run the 'Share' server - specify the port it will run on
    • from device, load the Stanza app
    • configure host:port as a server in Stanza
  • Using Amazon Kindle App
    • supports MOBI
    • Sign into Amazon, access Kindle Store (search bar)
    • access Manage My Devices (top), then Devices (left)
    • set up Personal Documents with a mail-to email address
    • email the MOBI using the email address
    • push to your device when doc appears under Personal Documents
Should I use DRM?
  • This only seems to cause trouble - and a barrier to sales!!
  • At this point, it is far easier just to download your book than to steal it - it's not worth trying to copy your book for the 3 or 4 bucks it would cost - unless you are a teenage hacker

Friday, March 1, 2013

EmberJS - Putting a REST service behind the TodoMVC app

Background
I'm trying to select a front-end framework for new apps - who isn't these days? - and in researching I came across Addy Osmani and company's fantastic bit of work:  http://addyosmani.github.com/todomvc/

After looking at this and some other stuff I decided to give Ember a try.

So why not start by hooking up a real REST service to this ToDo app?  But first I would need to understand how Ember communicates with the server.  A little research came up with the JSON Interface section in this post.

Implementation
I'm working with a Grails server, so providing the REST interface was pretty easy, in theory.

In practice, I decided to use a plugin for the REST interaction, and chose the json-rest-api for its simplicity.  However I soon found out that it would not work out of the box - it needed upgrading to Grails2.1, and it did not speak the dialect of REST that Ember prefers.

So I set about modifying the plugin to support Ember-style, and maintain its original style as well. The resulting fork of the json-rest-api plugin is here.

Here are steps I took:

create grails app
  • grails create-app todo
  • add Todo domain class with fields from TodoMVC todo.js

install json-rest-api
  • added grails.plugin.location to the BuildConfig 
    • I already had this project downloaded locally
    • using grails.plugin.location means changes to the plugin are automatically picked up
        // Adding Plugin-in:  grails-json-rest
        grails.plugin.location.jsonrest = '/opt/projects/grails-json-rest-api'
    
  • changes to Todo domain class:
    • add static 'expose' to Todo domain class
    • add toJSON() and fromJSON() methods - my enhancement to the json-rest-api plugin to support i18n and custom rendering
class Todo {
    
    String title
    boolean isCompleted

    static constraints = {
        title(blank:false, nullable:false,maxSize:64)
        isCompleted(default:false)
    }
    
    
    String toString() {
        StringBuilder sb = new StringBuilder()
        sb.append("\n    id:    ").append(id)
        sb.append("\n    Title:    ").append(title)
        sb.append("\n    Completed:    ").append(isCompleted)
        sb.toString()
    }
    
    
    // --- json-rest-api artifacts --- 
    
    static expose = 'todo' // Expose as REST API using json-rest-api plugin
                           //   this will be the entity name on the URL
    static api = [
        // If allowing json-rest-api to use 'as JSON' to render, you may exclude
        //    unwanted fields here (done with its registered ObjectMarshaller)
        excludedFields: [ "attached", "errors", "properties" ],
        // You may override how the list() operation performs its search here
        list : { params -> Todo.list(params) },
        count: { params -> Todo.count() }
    ]

    
    /*
    // This is the standard way to override JSON marshalling for a class
    //    It uses a ClosureOjectMarshaller[sic] to select fields for marshalling 
    //    It is less efficient for the plugin which is based on JSONObject, but this will be
    //    used if you do not define a 'toJSON' method.
    // NOTE: if using this approach, the json-rest-api marshaller will NOT be used, hence the
    //      api.excludedFields if defined will be ignored
    // Example taken from http://grails.org/Converters+Reference
    static {
        grails.converters.JSON.registerObjectMarshaller(Todo) {
           // you can filter here the key-value pairs to output:
           return it.properties.findAll {k,v -> k != 'passwd'}
          }
    }
    */
    
    /**
     * Rending this object into a JSONObject; allows more flexibility and efficiency in how
     * the object is eventually included in larger JSON structures before ultimate rendering;
     * MessageSource offered for i18n conversion before exporting for user audience.
     * @param messageSource
     * @return
     */
    JSONObject toJSON(def messageSource) {
        JSONObject json = new JSONObject()
        json.put('id', id)
        json.put('title', title)
        json.put('isCompleted', isCompleted)
        return json
    }

    /**
     * Custom bind from JSON; this has efficiency since the grails request.JSON object offers
     *    a JSONObject directly
     * @param json
     */
    void fromJSON (JSONObject json) {
        [
            "title"
        ].each(JSONUtil.optStr.curry(json, this))
       [
            "isCompleted"
        ].each(JSONUtil.optBoolean.curry(json, this))
    }
}

install functional testing plugin
  • grails install-plugin functional-test
  • required to test the json-rest-api plugin (my change)

Added logging into Config.groovy

inside the environment {} block; also added similar to development {}

    
test {
        grails.logging.jul.usebridge = false
        log4j = {
            appenders {
                rollingFile name:"todo", maxFileSize:"10000KB", maxBackupIndex:10, file:"logs/todo.log",layout:pattern(conversionPattern: '%d{yyyy-MM-dd HH:mm:ss,SSS z} [%t] %-5p[%c]: %m%n')
                console name:'stdout', layout: pattern(conversionPattern: '%d{dd-MM-yyyy HH:mm:ss,SSS} %5p %c{1} - %m%n')
                //console name:'stacktrace'
            }
            
            debug 'grails.app','com.gargoylesoftware.htmlunit.WebClient','org.grails.plugins.rest',additivity = true
            warn 'grails.app.services.grails.buildtestdata','BuildTestDataGrailsPlugin','grails.buildtestdata',
                  'org.codehaus.groovy','org.grails.plugin','grails.spring','net.sf.ehcache','grails.plugin',
                  'org.apache','com.gargoylesoftware.htmlunit','org.codehaus.groovy.grails.orm.hibernate','org.hibernate'
            
            root {
                debug 'stdout', 'todo'
                additivity = true
            }
        }
    }

create Todo functional test
  • using a Generic Mixin test class that I added into the json-rest-api project, resulting functional test class looks like:

@Mixin(GenericRestFunctionalTests)
class TodoFunctionalTests extends BrowserTestCase {

    def log = LogFactory.getLog(getClass())
    def messageSource

    void setUp() {
        super.setUp()
    }
    
    void tearDown() {
        super.tearDown()
    }

    
    void testList() {
        genericTestList(new Todo(title:"title.one"))
    }
    
    void testCreate() {
        genericTestCreate(new Todo(title:"title.one"))
    }
    
    void testShow() {
        genericTestShow(new Todo(title:"title.one"))
    }
    
    void testUpdate() {
        genericTestUpdate(new Todo(title:"title.one"), [title:"title.two"])
    }
    
    void testDelete() {
        genericTestDelete(new Todo(title:"title.one"))
    }
}
 
Wiring up the Ember interface
Time to modify the TodoMVC project to hook it up to my Grails app. 
  • pulled the TodoMVC source into my Grails project
  • modified store.js to configure the REST adapter - default is DS.RESTAdapter, but some changes were required:
  • modified the namespace to match my context and path that json-rest-api listens to (/api)
  • had to extend the built-in RESTSerializer to stop its crazy conversion of my camel-case field names into underscore versions
    // Override the default behaviour of the RESTSerializer to not convert
    //   my camelized field names into underscored versions
    Todos.TodoRESTAdapter = DS.RESTSerializer.extend({
       keyForAttributeName: function(type, name) {
         return name;
         //return Ember.String.decamelize(name);  // this is the default behaviour
       },
    
       keyForBelongsTo: function(type, name) {
         var key = this.keyForAttributeName(type, name);
    
         if (this.embeddedType(type, name)) {
           return key;
         }
    
         return key + "Id";
       },
    
       keyForHasMany: function(type, name) {
         var key = this.keyForAttributeName(type, name);
    
         if (this.embeddedType(type, name)) {
           return key;
         }
    
         return this.singularize(key) + "Ids";
       }
    });
    
    
    Todos.Store = DS.Store.extend({
     revision: 11,
        adapter: DS.RESTAdapter.create({ 
         bulkCommit: false, 
         namespace: "todo/api",
         serializer: Todos.TodoRESTAdapter
        })
    });
    
  • found out that Ember sends the entity name to the server in the plural form sometimes, and the json-rest-api plugin does not like this; modified the plugin to account for this. See this other post for the breakdown of Ember's REST dialect.





  • Found out that running in Tomcat7 did not allow changes to my JS files (grrr....)

    • Ran instead via grails run-app, and added a logger to the 'development' env in Config.groovy in support of this
    • (yes I tried fixing Tomcat by disabling caching/antiLocking in the servlet context)

    Result

    And voila - after modifying the REST plugin, the Grails app was pretty easy to accomodate to the Ember pulls. The resulting app is located here.

    Installing

    Download the app
    git clone https://github.com/kentbutler/todomvc-grails-emberjs.git

    Download the fork of the grails-json-rest-api (at least for now)
    git clone https://github.com/kentbutler/grails-json-rest-api.git

    Place them alongside each other and test the app by opening a console inside the directory and run:
    grails test-app -functional

    If tests pass then run the app via
    grails run-app

    If they do not pass, ensure the path to the json-rest-api inside of grails-app/conf/BuildConfig.groovy accurately locates the grails-json-rest-api plugin.



    EmberJS, Notes and Gotchas

    JSON Interface
    Description of the expected REST interface, adapted from http://stackoverflow.com/questions/14922623/what-is-the-complete-list-of-expected-json-responses-for-ds-restadapter :

    ContextServerURLMethodReq. DataResp. Data
    Get list of all/usersGET{"users":[{...},{...}]}
    Get one/users/123GET{"user":{...}}
    Create/usersPOST{"user":{...}}{"user":{...}}
    Update/users/123PUT{"user":{...}}{"user":{...}}
    Delete/users/123DELETEN/Anull
    Create in bulk/usersPOST{"users":[{...},{...}]}{"users":[{...},{...}]}
    Update in bulk/users/bulkPUT{"users":[{...},{...}]}{"users":[{...},{...}]}
    Delete in bulk/users/bulkDELETE{"users":[1,2]}{"users":[1,2]}

    This is EmberJS's documentation on this:
    http://emberjs.com/guides/models/the-rest-adapter/#toc_the-rest-adapter

    Ember Gotchas!
    • Ember tolerates no unmapped fields in your JSON results! Presence of these will result in an Ember.assert() error, which in Firefox produced no logging whatsoever, just a silent failure!
      • Example: if your data looks like this {"users":[{"name":"bob","id":3}], "count":1}
        and Ember does not know about the 'count' field, JSON response processing will abort
    • Ember transforms camel-cased model field names into underscored names when emitting JSON, and expects underscored names in retreivals:
      • e.g. model field 'isCompleted' will be sent in JSON updates as 'is_completed' - and must be sent as 'is_completed' as well
      • WORKAROUND: you must explicitly map the attribute in the RESTAdapter - see the EmberJS documentation here
      • or see the related Question below for a workaround
    Questions
    window.Todos.TodoRESTAdapter = DS.RESTSerializer.extend({
       keyForAttributeName: function(type, name) {
         return name;
       },
    
       keyForBelongsTo: function(type, name) {
         var key = this.keyForAttributeName(type, name);
    
         if (this.embeddedType(type, name)) {
           return key;
         }
    
         return key + "Id";
       },
    
       keyForHasMany: function(type, name) {
         var key = this.keyForAttributeName(type, name);
    
         if (this.embeddedType(type, name)) {
           return key;
         }
    
         return this.singularize(key) + "Ids";
       }
    });
    
    and then use this in your Stores:
    Todos.Store = DS.Store.extend({
     revision: 11,
        adapter: DS.RESTAdapter.create({ 
         bulkCommit: false, 
         namespace: "todo/api"
         serializer: Todos.TodoRESTAdapter
        })
    });
    
    • Q: How to do the analagous replacement of underscore() when serializer is serializing back to REST?
      • A: Here is how serializing to REST gets done:
        • base class DS.Serializer.serialze() is invoked
        • calls DS.Serializer().addAttributes() which loops through all attributes
        • calls DS.Serializer().addAttribute() which can be overridden
          • calls _keyForAttributeName()
            • eventually calls keyForAttributeName() in the Serializer
            • this is where we override
    • Q: how should I represent nested data elements?

    Execution of find()
    • findAll: function(store, type, since)
      • contains a 'success': callback - set breakpoint here
      • calls didFindAll()
        • uses a Loader, acquired as DS.loaderFor(store), and a Serializer
        • invokes serializer.extractMany(loader, payload, type)
          • where payload is your data! and type = model name
          • stepping into this puts you into a wrapper - step again into the 'apply()'
          • first parses sideloaded data using sideload()
            • see this for description of sideloading:
          • extractRecordRepresentation()
            • getMappingForType()
            • if [ should sideload ]
              • loader.sideload()
            • else
              • loader.load()
                • store.load()
                  • this adds a prematerialization entry into a list for later parsing
                  • somewhere much deeper in the stack, we get to....
                  • DS.RESTSerializer.keyForAttributeName()
                    • which calls Ember.String.decamelize()
                    • this is where underscores are introduced
    Pluralizing Strategy
    • executed in method: pluralize()
    • option 1: provide a mapping in definition of the RESTAdapter, as ....

      adapter: DS.RESTAdapter.create({ bulkCommit: false,
    plurals: {"security": "securities"} })
    • option 2: defaults to name + "s"
    Data Representation Internals, Notes on
    • maintains a 'stringCache' which JSON string field names are looked up against
      • these are given codes like 'st295' where the # is the field uuid