Miniature ActiveRecord for RubyMotion
Everything you need to start using SQLite as the datastore for your RubyMotion app.
- Installation
- MotionRecord::Base
- MotionRecord::Schema
- MotionRecord::Scope
- MotionRecord::Serialization
- MotionRecord::Association
🐢 Android support should be coming soon
Add this line to your Gemfile:
gem "motion_record"
On iOS, MotionRecord uses motion-sqlite3 as a wrapper for connecting to SQLite, so add these too:
gem "motion-sqlite3"
# Requires the most recent unpublished version of motion.h
# https://github.com/kastiglione/motion.h/issues/11
gem "motion.h", :git => "https://github.com/kastiglione/motion.h"
And then execute:
$ bundle
MotionRecord::Base provides a superclass for defining objects which are stored in the database.
class Message < MotionRecord::Base
# That's all!
end
Attribute methods are inferred from the associated SQLite table definition.
message = Message.new(subject: "Welcome!")
# => #<Message: @id=nil @subject="Welcome!" @body=nil, @created_at=nil ...>
Manage persistence with create
, save
, destroy
, and persisted?
message = Message.create(subject: "Welcome!")
message.body = "If you have any questions, just ask us :)"
message.save
# SQL: UPDATE messages SET subject = ?, body = ?, ... WHERE id = ?
# Params: ["Welcome!", "If you have any questions, just ask :)", ..., 1]
message.destroy
message.persisted?
# => false
If any of the columns are named created_at
or updated_at
then they are
automatically serialized as Time objects and set
to Time.now
when the record is created or updated.
Define and run all pending SQLite migrations with the up!
DSL.
def application(application, didFinishLaunchingWithOptions:launchOptions)
MotionRecord::Schema.up! do
migration 1, "Create messages table" do
create_table :messages do |t|
t.text :subject, null: false
t.text :body
t.integer :read_at
t.integer :remote_id
t.float :satisfaction, default: 0.0
t.timestamps
end
end
migration 2, "Index messages table" do
add_index :messages, :remote_id, :unique => true
add_index :messages, [:subject, :read_at]
end
end
# ...
end
By default, MotionRecord will print all SQL statements and use a file named
"app.sqlite3"
in the application's Application Support folder. To disable
logging (for release) or change the filename, pass configuration options to up!
resource_file = File.join(NSBundle.mainBundle.resourcePath, "data.sqlite3")
MotionRecord::Schema.up!(file: resource_file, debug: false) # ...
You can also specify that MotionRecord should use an in-memory SQLite database which will be cleared every time the app process is killed.
MotionRecord::Schema.up!(file: :memory) # ...
Build scopes on MotionRecord::Base classes with where
, order
and limit
.
Message.where(body: nil).order("read_at DESC").limit(3).find_all
Run queries on scopes with exists?
, first
, find
, find_all
, pluck
,
update_all
, and delete_all
.
Message.where(remote_id: 2).exists?
# => false
Message.find(21)
# => #<Message @id=21 @subject="What's updog?" ...>
Message.where(read_at: nil).pluck(:subject)
# => ["What's updog?", "What's updog?", "What's updog?"]
Message.where(read_at: nil).find_all
# => [#<Message @id=20 ...>, #<Message @id=21 ...>, #<Message @id=22 ...>]
Message.where(read_at: nil).update_all(read_at: Time.now.to_i)
Run calculations on scopes with count
, sum
, maximum
, minimum
, and
average
.
Message.where(subject: "Welcome!").count
# => 1
Message.where(subject: "How do you like the app?").maximum(:satisfaction)
# => 10.0
SQLite has a very limited set of datatypes (TEXT, INTEGER, and REAL), but you can easily store other objects as attributes in the database with serializers.
MotionRecord provides a built-in serializer for Time objects to any column datatype.
class Message < MotionRecord::Base
serialize :read_at, :time
end
Message.create(subject: "Hello!", read_at: Time.now)
# SQL: INSERT INTO messages (subject, body, read_at, ...) VALUES (?, ?, ?...)
# Params: ["Hello!", nil, 1420099200, ...]
Message.first.read_at
# => 2015-01-01 00:00:00 -0800
Boolean attributes can be serialized to INTEGER columns where 0 and NULL are
false
and any other value is true
.
class Message < MotionRecord::Base
serialize :satisfaction_submitted, :boolean
end
Objects can also be stored to TEXT columns as JSON.
class Survey < MotionRecord::Base
serialize :response, :json
end
survey = Survey.create(response: {nps: 10, what_can_we_improve: "Nothing :)"})
# SQL: INSERT INTO surveys (response) VALUES (?)
# Params: ['{"nps":10, "what_can_we_improve":"Nothing :)"}']
survey
# => #<Survey: @id=1 @response={"nps"=>10, "what_can_we_improve"=>"Nothing :)"}>
RubyMotion doesn't have a Date class, but as long as you're okay with using Time objects with only the date attributes, you can serialize them to TEXT columns:
class User < MotionRecord::Base
serialize :birthday, :date
end
drake = User.create(birthday: Time.new(1986, 10, 24))
# SQL: INSERT INTO users (birthday) VALUES (?)
# Params: ["1986-10-24"]
# => #<User: @id=1, @birthday=1986-10-24 00:00:00 UTC>
To write a custom serializer, extend MotionRecord::Serialization::BaseSerializer
and provide your class to serialize
instead of a symbol.
class MoneySerializer < MotionRecord::Serialization::BaseSerializer
def serialize(value)
raise "Wrong column type!" unless @column.type == :integer
value.cents
end
def deserialize(value)
raise "Wrong column type!" unless @column.type == :integer
Money.new(value)
end
end
class Purchase < MotionRecord::Base
serialize :amount_paid_cents, MoneySerializer
end
Please do!
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request