• Stars
    star
    172
  • Rank 221,201 (Top 5 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 11 years ago
  • Updated about 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Core Data Query for RubyMotion

Streamlined Core Data for RubyMotion

Core Data Query (CDQ) is a library to help you manage your Core Data stack while using RubyMotion. It uses a data model file, which you can generate in XCode, or you can use ruby-xcdm.

Build Status Gem Version

CDQ is maintained by Infinite Red, a web and mobile development company based in Portland, OR and San Francisco, CA.

Get Started

  1. Introducing CDQ
  2. Greenfield Quick Start Tutorial
  3. Cheat Sheet
  4. API docs

Introducing CDQ

CDQ began its life as a fork of MotionData, but it became obvious I wanted to take things in a different direction, so I cut loose and ended up rewriting almost everything. If you pay attention, you can still find the genetic traces, so thanks to @alloy for sharing his work and letting me learn so much.

CDQ aims to streamline the process of getting you up and running Core Data, while avoiding too much abstraction or method pollution on top of the SDK. While it borrows many ideas from ActiveRecord (especially AREL), it is designed to harmonize with Core Data's way of doing things first.

I am actively developing and improving CDQ (updated February 2015) so if you have trouble or find a bug, please open a ticket!

Why use a static Data Model?

By using a real data model file that gets compiled and included in your bundle, you can take advantage of automatic migration, which simplifies managing your schema as it grows, if you can follow a few simple rules.

Installing

$ gem install cdq
$ motion create my_app # if needed
$ cd my_app
$ cdq init

This way assumes you want to use ruby-xcdm. Run cdq -h for list of more generators.

Using Bundler:

gem 'cdq'

If you want to see bleeding-edge changes, point Bundler at the git repo:

gem 'cdq', git: 'git://github.com/infinitered/cdq.git'

Setting up your stack

You will need a data model file. If you've created one in XCode, move or copy it to your resources file and make sure it's named the same as your RubyMotion project. If you're using ruby-xcdm (which I highly recommend) then it will create the datamodel file automatically and put it in the right place.

Now include the setup code in your app_delegate.rb file:

class AppDelegate
  include CDQ

  def application(application, didFinishLaunchingWithOptions:launchOptions)
    cdq.setup
    true
  end
end

That's it! You can create specific implementation classes for your entities if you want, but it's not required. You can start running queries on the console or in your code right away.

Schema

The best way to use CDQ is together with ruby-xcdm, which is installed as a dependency. For the full docs, see its github page, but here's a taste. Schema files are found in the "schemas" directory within your app root, and they are versioned for automatic migrations, and this is what they look like:

  schema "0001 initial" do

    entity "Article" do
      string    :body,        optional: false
      integer32 :length
      boolean   :published,   default: false
      datetime  :publishedAt, default: false
      string    :title,       optional: false

      belongs_to :author
    end

    entity "Author" do
      float :fee
      string :name, optional: false

      # Deleting an author will delete all associated articles
      has_many :articles, deletionRule: "Cascade"
    end

  end

Ruby-xcdm translates these files straight into the XML format that Xcode uses for datamodels.

Boolean Values

Since CoreData stores boolean values as an NSNumber, cdq provides helper methods to allow you to get the boolean value of the property. Take the Article model from above with the boolean:published. If you call published directly you'll get the NSNumber 0 or 1. If you call published? you'll get a boolean true or false

article_1 = Article.create(published: true)
article_2 = Article.create(published: false)

article_1.published # => 1
article_2.published # => 0

article_1.published? # => true
article_2.published? # => false

Context Management

Managing NSManagedObjectContext objects in Core Data can be tricky, especially if you are trying to take advantage of nested contexts for better threading behavior. One of the best parts of CDQ is that it handles contexts for you relatively seamlessly. If you have a simple app, you may never need to worry about contexts at all.

Nested Contexts

For a great discussion of why you might want to use nested contexts, see here.

CDQ maintains a stack of contexts (one stack per thread), and by default, all operations on objects use the topmost context. You just call cdq.save and it saves the whole stack. Or you can get a list of all the contexts in order with cdq.contexts.all and do more precise work.

To access the cdq object from a class method inside a class that is not a CDQManagedObject subclass, make sure to include the CDQ module in your class like this:

class MyClass
  class << self
    include CDQ

    def my_class_method
      # Do something
      cdq.save
    end
  end
end

# Elsewhere
MyClass.my_class_method

Settings things up the way you want is easy. Here's how you'd set it up for asynchronous saves:

  cdq.contexts.push(:root)
  cdq.contexts.push(:main)

This pushes a private queue context onto the bottom of the stack, then a main queue context on top of it. Since the main queue is on top, all your data operations will use that. cdq.save then saves the main context, and schedules a save on the root context.

In addition, since these two contexts are globally important, it makes them available at cdq.contexts.main and cdq.contexts.root.

Temporary Contexts

From time to time, you may need to use a temporary context. For example, on importing a large amount of data from the network, it's best to process and load into a temporary context (possibly in a background thread) and then move all the data over to your main context all at once. CDQ makes that easy too:

  cdq.background do

    # Your work here

    cdq.save
  end

Object Lifecycle

Creating

  Author.create(name: "Le Guin", publish_count: 150, first_published: 1970)
  Author.create(name: "Shakespeare", publish_count: 400, first_published: 1550)
  Author.create(name: "Blake", publish_count: 100, first_published: 1778)
  cdq.save

CDQ will automatically set the object's property created_at to Time.now if it exists. If you want to use this ActiveRecord-like automatic attribute, make sure to add datetime :created_at to your schema's model definition.

Reading

  author = Author.create(name: "Le Guin", publish_count: 150, first_published: 1970)
  author.name # => "Le Guin"
  author.publish_count # => 150
  author.attributes # => { "name" => "Le Guin", "publish_count" => 150, "first_published" => 1970 }

Updating

  author = Author.first
  author.name = "Ursula K. Le Guin"
  cdq.save

You can also update multiple attributes of a single object:

  author = Author.first
  author.update(name: "Mark Twain", publish_count: 30, first_published: 1865)
  cdq.save

The update command will raise an UnknownAttributeError if you try and set an attribute that doesn't exist on the object so it's good practice to sanitize the data before you call update:

  new_author_data = {
    name: "Mark Twain",
    publish_count: 30,
    first_published: 1865,
    some_attribute_that_doesnt_exist_on_author: "balderdash!"
  }  
  sanitized = new_author_data.keep_if{|k,_| Author.attribute_names.include?(k) }

  author = Author.first
  author.update(sanitized)
  cdq.save

NOTE Custom class methods will have to include CDQ in order to have access to the cdq object. If you're calling cdq from a class method, you also have to extend CDQ.

CDQ will automatically set the object's property updated_at to Time.now if it exists. If you want to use this ActiveRecord-like automatic attribute, make sure to add datetime :updated_at to your schema's model definition.

Deleting

  author = Author.first
  author.destroy
  cdq.save

Queries

A quick aside about queries in Core Data. You should avoid them whenever possible in your production code. Core Data is designed to work efficiently when you hang on to references to specific objects and use them as you would any in-memory object, letting Core Data handle your memory usage for you. If you're coming from a server-side rails background, this can be pretty hard to get used to, but this is a very different environment. So if you find yourself running queries that only return a single object, consider rearchitecting. That said, queries are sometimes the only solution, and it's very handy to be able to use them easily when debugging from the console, or in unit tests.

All of these queries are infinitely daisy-chainable, and almost everything is possible to do using only chained methods, no need to drop into NSPredicate format strings unless you want to.

Here are some examples. See the cheat sheet for a complete list.

Conditions

  Author.where(:name).eq('Shakespeare')
  Author.where(:publish_count).gt(10)
  Author.where(name: 'Shakespeare', publish_count: 15)
  Author.where("name LIKE %@", '*kesp*')
  Author.where("name LIKE %@", 'Shakespear?')

Sorts, Limits and Offsets

  Author.sort_by(:created_at).limit(1).offset(10)
  Author.sort_by(:created_at, order: :descending)
  Author.sort_by(:created_at, case_insensitive: true)

Conjunctions

  Author.where(:name).eq('Blake').and(:first_published).le(Time.local(1700))

  # Multiple comparisons against the same attribute
  Author.where(:created_at).ge(yesterday).and.lt(today)

Nested Conjunctions

  Author.where(:name).contains("Emily").and(cdq(:pub_count).gt(100).or.lt(10))

Calculations

  Author.sum(:fee)
  Author.average(:fee)
  Author.min(:fee)
  Author.max(:fee)
  Author.where(:name).eq("Emily").sum(:fee)

Fetching

Like ActiveRecord, CDQ will not run a fetch until you actually request specific objects. There are several methods for getting at the data:

  • array
  • first
  • last
  • each
  • []
  • map
  • Anything else in Enumerable

Dedicated Models

If you're using CDQ in a brand new project, you'll probably want to use dedicated model classes for your entities. familiar-looking and natural syntax for queries and scopes:

  class Author < CDQManagedObject
  end

Named Scopes

You can save up partially-constructed queries for later use using named scopes, even combining them seamlessly with other queries or other named scopes:

  class Author < CDQManagedObject
    scope :a_authors, where(:name).begins_with('A')
    scope :prolific, where(:publish_count).gt(99)
  end

  Author.prolific.a_authors.limit(5)

Using CDQ with a pre-existing model

If you have an existing app that already manages its own data model, you can still use CDQ, overriding its stack at any layer:

cdq.setup(context: App.delegate.mainContext) # don't set up model or store coordinator
cdq.setup(store: App.delegate.persistentStoreCoordinator) # Don't set up model
cdq.setup(model: App.delegate.managedObjectModel) # Don't load model

You cannot use CDQManagedObject as a base class when overriding this way, you'll need to use the master method, described below. If you have an existing model and want to use it with CDQManagedObject without changing its name, You'll need to use a cdq.yml config file. See CDQConfig.

Working without model classes using the master method

If you need or want to work without using CDQManagedObject as your base class, you can use the cdq()master method. This is a "magic" method, like rmq() in RubyMotionQuery or $() in jQuery, which will lift whatever you pass into it into the CDQ universe. The method is available inside all UIResponder classes (so, views and controllers) as well as in the console. You can use it anywhere else by including the model CDQ into your classes. To use an entity without a model class, just pass its name as a string into the master method, like so

  cdq('Author').where(:name).eq('Shakespeare')
  cdq('Author').where(:publish_count).gt(10)
  cdq('Author').sort_by(:created_at).limit(1).offset(10)

Anything you can do with a model, you can also do with the master method, including defining and using named scopes:

  cdq('Author').scope :a_authors, cdq(:name).begins_with('A')
  cdq('Author').scope :prolific, cdq(:publish_count).gt(99)

NOTE: strings and symbols are NOT interchangeable. cdq('Entity') gives you a query generator for an entity, but cdq(:attribute) starts a predicate for an attribute.

Reserved model attributes

CDQ does some smart automatic attribute setting. If you add attributes :created_at and/or :updated_at to a model in your schema file, whenever a record is created or updated, these properties will be updated accordingly. Therefore, you can not define your own :created_at or :updated_at model attributes. These attributes must be of type datetime. Note that these attributes aren't set until you call cdq.save

Example:

schema "0001 initial" do
  entity "Author" do
    string :name, optional: false

    datetime :created_at
    datetime :updated_at
  end
end
a = Author.create(name: "Le Guin")
# Notice that the properties aren't set yet
#
# <Author: 0x1175f9540> (entity: Author; id: 0x117504810
# <x-coredata:///Author/tA4E22210-72CF-4272-BF2C-0C5C63A55B072> ; data: {
#     name: "Le Guin";
#     created_at: nil;
#     updated_at: nil;
# })

cdq.save

puts a # Original reference to created Author object
# <Author: 0x1175f9540> (entity: Author; id: 0x117504810
# <x-coredata:///Author/tA4E22210-72CF-4272-BF2C-0C5C63A55B072> ; data: {
#     name: "Le Guin";
#     created_at: 2015-08-19 20:44:40 +0000;
#     updated_at: 2015-08-19 20:44:40 +0000;
# })

a.name = "Some Other Guy"
puts a
# Note that nothing has changed except the name:
#
# <Author: 0x1175f9540> (entity: Author; id: 0x117504810
# <x-coredata:///Author/tA4E22210-72CF-4272-BF2C-0C5C63A55B072> ; data: {
#     name: "Some Other Guy";
#     created_at: 2015-08-19 20:44:40 +0000;
#     updated_at: 2015-08-19 20:44:40 +0000;
# })

cdq.save
puts a
# <Author: 0x1175f9540> (entity: Author; id: 0x117504810
# <x-coredata:///Author/tA4E22210-72CF-4272-BF2C-0C5C63A55B072> ; data: {
#     name: "Some Other Guy";
#     created_at: 2015-08-19 20:44:40 +0000;
#     updated_at: 2015-08-19 20:47:40 +0000;
# })

Also note that you should never use object_id as a model attribute as it will conflict with an internally generated property.

iCloud

Removed as of version 2.0.0. If you still need this, pin cdq gem to before version 2.0.0

As of version 0.1.10, there is some experimental support for iCloud, written by @katsuyoshi. Please try it out and let us know how it's working for you. To enable, initialize like this:

  cdq.stores.new(iCloud: true, container: "com.your.container.id")

You can also set up iCloud in your cdq.yml file.

Documentation

Things that are currently missing

  • There is no facility for custom migrations yet
  • There are no explicit validations (but you can define them on your data model)
  • Lifecycle Callbacks or Observers

Tips

If you need, you could watch SQL statements by setting the following launch argument through args environment variable:

$ rake args='-com.apple.CoreData.SQLDebug 3'

com.apple.CoreData.SQLDebug takes a value between 1 and 3; the higher the value, the more verbose the output.

Premium Support

CDQ, as an open source project, is free to use and always will be. Infinite Red offers premium CDQ support and general mobile app design/development services. Email us at [email protected] to get in touch with us for more details.

More Repositories

1

ignite

Infinite Red's battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more!
TypeScript
17,283
star
2

reactotron

A desktop app for inspecting your React JS and React Native projects. macOS, Linux, and Windows.
TypeScript
14,757
star
3

nsfwjs

NSFW detection on the client-side via TensorFlow.js
JavaScript
7,194
star
4

gluegun

A delightful toolkit for building TypeScript-powered command-line apps.
TypeScript
2,937
star
5

apisauce

Axios + standardized errors + request/response transforms.
JavaScript
2,789
star
6

thesis-phoenix

A lightweight, bolt-on, intuitive content editing system for Elixir/Phoenix websites. Star this repo and follow along with our progress!
Elixir
648
star
7

ignite-bowser

Bowser is now re-integrated into Ignite CLI! Head to https://github.com/infinitered/ignite to check it out.
TypeScript
615
star
8

solidarity

Solidarity is an environment checker for project dependencies across multiple machines.
TypeScript
614
star
9

ignite-andross

The original React Native boilerplate from Infinite Red - Redux, React Navigation, & more
JavaScript
475
star
10

ChainReactApp2017

The official Chain React Conf 2017 App
JavaScript
437
star
11

rmq

RMQ - RubyMotionQuery
HTML
307
star
12

redpotion

We believe iPhone development should be clean, scalable, and fast with a language that developers not only enjoy, but actively choose. With the advent of Ruby for iPhone development the RubyMotion community has combined and tested the most active and powerful gems into a single package called RedPotion
Ruby
234
star
13

ChainReactApp2019

The Chain React 2019 Conference App
TypeScript
165
star
14

react-native-mlkit

The definitive MLKit wrapper for React Native and Expo
TypeScript
124
star
15

nsfwjs-mobile

NSFWjs in React Native
JavaScript
112
star
16

ChainReactApp2023

The official Chain React App for #ChainReact2023, written in React Native
TypeScript
110
star
17

reactotron-react-native

Reactotron's react-native client.
TypeScript
110
star
18

ruby-xcdm

Ruby XCDM
Ruby
94
star
19

bluepotion

Like RedPotion, but for Android
Ruby
74
star
20

ProMotion-menu

RubyMotion gem allowing you to easily setup a facebook or Path style hidden slide menu easily with the ProMotion gem.
Ruby
74
star
21

ramdasauce

Ramda smothered in saucy helpers.
JavaScript
69
star
22

awesome-react-testing

React and React Native testing tools and strategies
67
star
23

firebird

Template for Phoenix 1.3 projects
Elixir
66
star
24

phoenix_base

Template project for Phoenix
Elixir
65
star
25

flipper-plugin-reactotron

A plugin for the Flipper desktop application
TypeScript
63
star
26

ignite-cookbook

Ignite Cookbook for React Native
CSS
47
star
27

ignite-example-login

This is an example Ignited app that shows how to integrate a login screen with redux-aware react navigation
JavaScript
47
star
28

solidarity-react-native

Solidarity snapshot plugin for React Native projects
JavaScript
47
star
29

ai-lab

Library of components for TensorFlow.js in web frameworks.
TypeScript
46
star
30

ionicons-version-3-search

Quickly identify IonIcons from version 3, with version 2 names as tags!
JavaScript
41
star
31

apex

Apex: the RubyMotion web framework for OS X.
Ruby
31
star
32

ir-docs

Omnibus Documentation for Infinite Red Opensource
CSS
26
star
33

ignite-ir-boilerplate-2016

[Not supported] A boilerplate for Ignite with best practices we've learned from 2015.
JavaScript
26
star
34

ignite-bowser-examples

This repository is out of date and is archived here for historical purposes only. See current Ignite Bowser for more relevant examples.
TypeScript
26
star
35

maybe

Access Elixir maps and structs, protected from `nil`
Elixir
25
star
36

open-source

Information and Guides for Infinite Red Open Source Projects
24
star
37

thesis-rails

Thesis: A Rails CMS that doesn't hijack your development workflow. [Deprecated]
Ruby
23
star
38

authority

Next-gen Elixir authentication specification
Elixir
22
star
39

babel-plugin-ignite-ignore-reactotron

Strips Reactotron from production builds for Ignite-based apps.
JavaScript
21
star
40

ChainReactApp2022

TypeScript
20
star
41

react-navx

[Experimental] Navigation and state management in one place for your React Native projects, featuring React Navigation and MobX / MST
TypeScript
20
star
42

mst-reference-pool

MST Reference Pool is a MobX-State-Tree extension that allows you to use references to a pool of model instances in any store.
TypeScript
20
star
43

ProMotion-iap

In-app purchases for ProMotion!
Ruby
19
star
44

ProMotion-form

Deprecated -- use ProMotion-XLForm
Ruby
19
star
45

ignite-maps

Painlessly add react-native-maps to your React Native app using Ignite and Ignite Maps.
EJS
19
star
46

ProMotion-push

Push notification support for ProMotion.
Ruby
18
star
47

rmq-example-app-image-browser

An example app for RubyMotionQuery (rmq). The user can query, browse, and zoom photos. Uses RMQ and AFMotion
Ruby
18
star
48

Parsistence

A nice wrapper for your Parse models on RubyMotion, complete with querying and much more.
Ruby
17
star
49

reactotron-react-js

Reactotron's react-dom client.
TypeScript
16
star
50

whitepotion

WhitePotion is just like RedPotion, but for OS X
Ruby
15
star
51

ProMotion-map

ProMotion::MapScreen gem. Extracted from ProMotion core.
Ruby
13
star
52

react-native-ai

13
star
53

motion-scene-kit

SceneKit stuff in RubyMotion
Ruby
13
star
54

middleman-template

A nice default project template for Middleman, the fantastic static site building tool.
CSS
12
star
55

harvest-invision-integration

Install this User Script via TamperMonkey to add Harvest Tracker to Invision Enterprise workflow pages
JavaScript
12
star
56

authority_ecto

Ecto integration for Authority https://github.com/infinitered/authority
Elixir
12
star
57

rmq-slide-over-control

A RubyMotion RMQ control that places a draggable view over another view, allowing the user to show more or less of the main view below
Ruby
12
star
58

keras-model-zoo

Ready to go, downloadable models for Keras
11
star
59

ignite-json-server

Ignite plugin that adds json-server to an Ignited project
JavaScript
10
star
60

reactotron-redux

The redux plugin for reactotron that allows tracking redux actions and state
TypeScript
10
star
61

reactotron-redux-saga

The redux saga plugin for reactotron. Allows for tracking of Redux Sagas
TypeScript
10
star
62

reactotron-core-client

The core client for all reactotron clients
TypeScript
10
star
63

ignite-redux-persist

An Ignite CLI plugin for Redux Persist
JavaScript
9
star
64

addon-storyshots

JavaScript
8
star
65

rmq-template

A template for RubyMotionQuery projects
Ruby
8
star
66

DiveIntoNative

Just scratching the surface of jumping into Native
Objective-C
7
star
67

ignite-dev-screens

Ignite DevScreens plugin for ignite-cli
JavaScript
7
star
68

tensorplayground

Playground for visual tensors and interactive code
JavaScript
7
star
69

mithril_pubsub

A PubSub layer for Mithril-architected apps
Elixir
6
star
70

ignite-animatable

An ignite plugin for react-native-animatable.
JavaScript
6
star
71

reactotron-core-server

The core reactotron server used by the reactotron app
TypeScript
6
star
72

redpotion-template

Ruby
5
star
73

bluepotion-template

Ruby
5
star
74

ignite-vector-icons

An ignite plugin for `react-native-vector-icons`.
JavaScript
5
star
75

jest-preset-ignite

Glues together TypeScript, React Native, and Jest.
JavaScript
5
star
76

ignite-redux-devtools

An Ignite CLI plugin for Redux DevTools
JavaScript
5
star
77

reactotron-mst

Reactotron's mobx-state-tree plugin
TypeScript
4
star
78

ignite-i18n

An ignite plugin for react-native-i18n.
JavaScript
4
star
79

reactotron-core-ui

TypeScript
4
star
80

ueberauth_dwolla

Ueberauth Plugin for OAuth through Dwolla
Elixir
3
star
81

ups

Address validation/normalization against UPS API
Elixir
3
star
82

ignite-ir-boilerplate

Boilerplate Index
3
star
83

whack-a-mole

A game to demonstrate lists, lifecycle methods, and fundamentals of React Native
JavaScript
3
star
84

tutorial-movie

A demo CLI app, powered by Gluegun
TypeScript
3
star
85

ignite-next-js

Ignite Next.js website boilerplate with Next.js, MySQL, and Github authentication
JavaScript
3
star
86

elavon-elixir

Native elixir client for USBank Elavon Converge API
Elixir
3
star
87

ignite-plugins

The Official Ignite Plugin Registry
3
star
88

eversign

Elixir wrapper for Eversign API
Elixir
2
star
89

MarqueeMark

Large text displayer.
Objective-C
2
star
90

reactotron-apisauce

Reactotron's apisauce plugin
TypeScript
2
star
91

msterymeat

mobx-state-tree proclivities
TypeScript
2
star
92

motion-conf

motion-conf is an easy to use configuration class generator for your RubyMotion apps.
Ruby
2
star
93

wpf-base

Base React Native Windows WPF project for bare bones use in upstream
C#
2
star
94

mobx-forge

TypeScript
2
star
95

cr-2024-intermediate-workshop-lessons

TypeScript
2
star
96

cr-2024-intermediate-workshop-template

TypeScript
2
star
97

orb-publish-docs

CircleCI Orb for publishing docs
Shell
1
star
98

TrainingAppDec2018

What we covered in the online Infinite Red Academy training on December 12, 2018
JavaScript
1
star
99

rmq-plugin-template

Template Repo for RMQ
HTML
1
star
100

solidarity-stacks

Community Solidarity files for well known software stacks
1
star