motion-loco 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,21 +37,33 @@ module Loco
37
37
  end
38
38
 
39
39
  # Change one or many properties from a hash of properties with values.
40
- # @param [Hash] properties
41
- def set_properties(hash)
40
+ # @param [Hash] properties_hash
41
+ def set_properties(properties_hash)
42
42
  # Set the initial property values from the given hash
43
- hash.each do |key, value|
43
+ properties_hash.each do |key, value|
44
44
  self.send("#{key}=", value)
45
45
  end
46
46
  end
47
47
 
48
+ # Change one or many properties from a hash of properties with values.
49
+ # Only updates attributes defined with #property.
50
+ # @param [Hash] properties_hash
51
+ def update_attributes(properties_hash)
52
+ self.class.get_class_properties.each do |property|
53
+ key = property[:name].to_sym
54
+ if properties_hash.has_key? key
55
+ self.setValue(properties_hash[key], forKey:key)
56
+ end
57
+ end
58
+ end
59
+
48
60
  def method_missing(method, *args, &block)
49
61
  if method.end_with?('_binding=') || method.end_with?('Binding=')
50
62
  method = method.gsub('_binding=', '').gsub('Binding=', '')
51
63
  if args.first.is_a?(String)
52
64
  if args.first =~ /^[A-Z]/
53
65
  split_args = args.first.split('.')
54
- target = Kernel.const_get(split_args.slice!(0)).instance
66
+ target = split_args.slice!(0).constantize.instance
55
67
  key_path = split_args.join('.')
56
68
  else
57
69
  target = self
@@ -61,9 +73,9 @@ module Loco
61
73
  target = args.first.first
62
74
  key_path = args.first.last
63
75
  end
64
- self.send("#{method}=", target.valueForKeyPath(key_path))
76
+ self.setValue(target.valueForKeyPath(key_path), forKey:method)
65
77
  register_observer(target, key_path) do
66
- self.send("#{method}=", target.valueForKeyPath(key_path))
78
+ self.setValue(target.valueForKeyPath(key_path), forKey:method)
67
79
  end
68
80
  else
69
81
  super
@@ -97,14 +109,14 @@ module Loco
97
109
 
98
110
  # Create the bindings for the computed properties and observers
99
111
  def initialize_bindings
100
- bindings = self.class.send(:get_class_bindings)
112
+ bindings = self.class.get_class_bindings
101
113
 
102
114
  bindings.each do |binding|
103
115
  binding[:proc].observed_properties.each do |key_path|
104
116
  register_observer(self, key_path) do
105
117
  new_value = binding[:proc].call(self)
106
118
  if binding[:name]
107
- self.send("#{binding[:name]}=", new_value)
119
+ self.setValue(new_value, forKey:binding[:name])
108
120
  end
109
121
  end
110
122
  end
@@ -133,14 +145,20 @@ module Loco
133
145
  end
134
146
 
135
147
  module ClassMethods
136
- def property(name, proc=nil)
137
- attr_accessor name
138
- if proc.nil?
139
- @class_properties = get_class_properties
140
- @class_properties << name
141
- else
148
+ def property(name, type=nil)
149
+ name = name.to_sym
150
+ @class_properties = get_class_properties
151
+
152
+ unless @class_properties.include? name
153
+ attr_accessor name
154
+ end
155
+
156
+ if type.is_a? Proc
142
157
  @class_bindings = get_class_bindings
143
- @class_bindings << { name: name, proc: proc }
158
+ @class_bindings << { name: name, proc: type }
159
+ else
160
+ type = type.to_sym unless type.nil?
161
+ @class_properties << { name: name, type: type }
144
162
  end
145
163
  end
146
164
 
@@ -152,14 +170,36 @@ module Loco
152
170
  # An array of the model's bindings
153
171
  # @return [Array]
154
172
  def get_class_bindings
155
- @class_bindings ||= []
173
+ if @class_bindings.nil?
174
+ @class_bindings = []
175
+ if self.superclass.respond_to? :get_class_bindings
176
+ @class_bindings.concat(self.superclass.get_class_bindings)
177
+ end
178
+ end
179
+ @class_bindings
156
180
  end
157
181
 
158
182
  # An array of the model's properties
159
183
  # used for saving the record
160
184
  # @return [Array]
161
185
  def get_class_properties
162
- @class_properties ||= []
186
+ if @class_properties.nil?
187
+ @class_properties = []
188
+ if self.superclass.respond_to? :get_class_properties
189
+ @class_properties.concat(self.superclass.get_class_properties)
190
+ end
191
+ end
192
+ @class_properties
193
+ end
194
+
195
+ def get_class_relationships
196
+ if @class_relationships.nil?
197
+ @class_relationships = []
198
+ if self.superclass.respond_to? :get_class_relationships
199
+ @class_relationships.concat(self.superclass.get_class_relationships)
200
+ end
201
+ end
202
+ @class_relationships
163
203
  end
164
204
 
165
205
  end
@@ -5,17 +5,11 @@ module Loco
5
5
  class RecordArray < Array
6
6
  include Observable
7
7
 
8
- def did_load
9
- # Override to perform actions after loading data
10
- end
11
- alias_method :didLoad, :did_load
12
-
13
8
  def load(type, data)
14
9
  self.removeAllObjects
15
10
  data.each do |item_data|
16
11
  self.addObject(type.new(item_data))
17
12
  end
18
- self.did_load
19
13
  self
20
14
  end
21
15
 
@@ -112,7 +112,7 @@ module Loco
112
112
  @autoresizing = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin
113
113
  else
114
114
  # Needs More Params
115
- NSLog('%@<Loco::View> Not enough params to position and size the view.', self.class)
115
+ NSLog('%@<Loco::UI::View> Not enough params to position and size the view.', self.class)
116
116
  end
117
117
  elsif self.top
118
118
  if self.left && self.right && self.height
@@ -145,7 +145,7 @@ module Loco
145
145
  @autoresizing = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleBottomMargin
146
146
  else
147
147
  # Needs More Params
148
- NSLog('%@<Loco::View> Not enough params to position and size the view.', self.class)
148
+ NSLog('%@<Loco::UI::View> Not enough params to position and size the view.', self.class)
149
149
  end
150
150
  elsif self.bottom
151
151
  if self.left && self.right && self.height
@@ -178,7 +178,7 @@ module Loco
178
178
  @autoresizing = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin
179
179
  else
180
180
  # Needs More Params
181
- NSLog('%@<Loco::View> Not enough params to position and size the view.', self.class)
181
+ NSLog('%@<Loco::UI::View> Not enough params to position and size the view.', self.class)
182
182
  end
183
183
  elsif self.left && self.right && self.height
184
184
  # FW, FTM, FBM
@@ -212,17 +212,17 @@ module Loco
212
212
  end
213
213
  else
214
214
  # Needs More Params
215
- NSLog('%@<Loco::View> Not enough params to position and size the view.', self.class)
215
+ NSLog('%@<Loco::UI::View> Not enough params to position and size the view.', self.class)
216
216
  end
217
217
 
218
218
  # Warn of any possible conflicts
219
219
  if self.top && self.bottom && self.height
220
- NSLog('%@<Loco::View> `top`, `bottom`, and `height` may conflict with each other. Only two of the three should be set.', self.class)
221
- NSLog('%@<Loco::View> top: %@, bottom: %@, height: %@', self.class, self.top, self.bottom, self.height)
220
+ NSLog('%@<Loco::UI::View> `top`, `bottom`, and `height` may conflict with each other. Only two of the three should be set.', self.class)
221
+ NSLog('%@<Loco::UI::View> top: %@, bottom: %@, height: %@', self.class, self.top, self.bottom, self.height)
222
222
  end
223
223
  if self.left && self.right && self.width
224
- NSLog('%@<Loco::View> `left`, `right`, and `width` may conflict with each other. Only two of the three should be set.', self.class)
225
- NSLog('%@<Loco::View> left: %@, right: %@, width: %@', self.class, self.left, self.right, self.width)
224
+ NSLog('%@<Loco::UI::View> `left`, `right`, and `width` may conflict with each other. Only two of the three should be set.', self.class)
225
+ NSLog('%@<Loco::UI::View> left: %@, right: %@, width: %@', self.class, self.left, self.right, self.width)
226
226
  end
227
227
 
228
228
  if @origin_x && @origin_y && @size_width && @size_height && @autoresizing
@@ -12,11 +12,12 @@ module Loco
12
12
  end
13
13
 
14
14
  def create_record(record, &block)
15
- BW::HTTP.post("#{self.url}/#{record.class.to_s.underscore.pluralize}.json", { payload: record.serialize(root: true) }) do |response|
15
+ type = record.class
16
+ BW::HTTP.post("#{self.url}/#{type.to_s.underscore.pluralize}.json", { payload: record.serialize }) do |response|
16
17
  if response.ok?
17
18
  error = Pointer.new(:id)
18
19
  data = NSJSONSerialization.JSONObjectWithData(response.body, options:JSON_OPTIONS, error:error)
19
- record.load(data[record.class.to_s.underscore][:id], data[record.class.to_s.underscore])
20
+ load(type, record, data)
20
21
  block.call(record) if block.is_a? Proc
21
22
  else
22
23
  Loco.debug("Responded with #{response.status_code}")
@@ -27,7 +28,8 @@ module Loco
27
28
  end
28
29
 
29
30
  def delete_record(record, &block)
30
- BW::HTTP.delete("#{self.url}/#{record.class.to_s.underscore.pluralize}/#{record.id}.json") do |response|
31
+ type = record.class
32
+ BW::HTTP.delete("#{self.url}/#{type.to_s.underscore.pluralize}/#{record.id}.json") do |response|
31
33
  if response.ok?
32
34
  block.call(record) if block.is_a? Proc
33
35
  else
@@ -39,11 +41,12 @@ module Loco
39
41
  end
40
42
 
41
43
  def find(record, id, &block)
42
- BW::HTTP.get("#{self.url}/#{record.class.to_s.underscore.pluralize}/#{id}.json") do |response|
44
+ type = record.class
45
+ BW::HTTP.get("#{self.url}/#{type.to_s.underscore.pluralize}/#{id}.json") do |response|
43
46
  if response.ok?
44
47
  error = Pointer.new(:id)
45
48
  data = NSJSONSerialization.JSONObjectWithData(response.body, options:JSON_OPTIONS, error:error)
46
- record.load(id, data[record.class.to_s.underscore])
49
+ load(type, record, data)
47
50
  block.call(record) if block.is_a? Proc
48
51
  else
49
52
  Loco.debug("Responded with #{response.status_code}")
@@ -58,7 +61,7 @@ module Loco
58
61
  if response.ok?
59
62
  error = Pointer.new(:id)
60
63
  data = NSJSONSerialization.JSONObjectWithData(response.body, options:JSON_OPTIONS, error:error)
61
- records.load(type, data[type.to_s.underscore.pluralize])
64
+ load(type, records, data)
62
65
  block.call(records) if block.is_a? Proc
63
66
  else
64
67
  Loco.debug("Responded with #{response.status_code}")
@@ -73,7 +76,7 @@ module Loco
73
76
  if response.ok?
74
77
  error = Pointer.new(:id)
75
78
  data = NSJSONSerialization.JSONObjectWithData(response.body, options:JSON_OPTIONS, error:error)
76
- records.load(type, data[type.to_s.underscore.pluralize])
79
+ load(type, records, data)
77
80
  block.call(records) if block.is_a? Proc
78
81
  else
79
82
  Loco.debug("Responded with #{response.status_code}")
@@ -88,7 +91,7 @@ module Loco
88
91
  if response.ok?
89
92
  error = Pointer.new(:id)
90
93
  data = NSJSONSerialization.JSONObjectWithData(response.body, options:JSON_OPTIONS, error:error)
91
- records.load(type, data[type.to_s.underscore.pluralize])
94
+ load(type, records, data)
92
95
  block.call(records) if block.is_a? Proc
93
96
  else
94
97
  Loco.debug("Responded with #{response.status_code}")
@@ -99,7 +102,8 @@ module Loco
99
102
  end
100
103
 
101
104
  def update_record(record, &block)
102
- BW::HTTP.put("#{self.url}/#{record.class.to_s.underscore.pluralize}/#{record.id}.json", { payload: record.serialize(root: true) }) do |response|
105
+ type = record.class
106
+ BW::HTTP.put("#{self.url}/#{type.to_s.underscore.pluralize}/#{record.id}.json", { payload: record.serialize }) do |response|
103
107
  if response.ok?
104
108
  block.call(record) if block.is_a? Proc
105
109
  else
@@ -110,6 +114,20 @@ module Loco
110
114
  record
111
115
  end
112
116
 
117
+ def serialize(record, options={})
118
+ json = {}
119
+ record.class.get_class_relationships.each do |relationship|
120
+ if relationship[:belongs_to]
121
+ key = "#{relationship[:belongs_to]}_id".to_sym
122
+ elsif relationship[:has_many]
123
+ key = "#{relationship[:has_many].to_s.singularize}_ids".to_sym
124
+ end
125
+ value = record.valueForKey(key)
126
+ json[key] = value if value
127
+ end
128
+ super(record, options, json)
129
+ end
130
+
113
131
  def url
114
132
  unless @url.nil?
115
133
  @url
@@ -0,0 +1,235 @@
1
+ module Loco
2
+
3
+ class SQLiteAdapter < Adapter
4
+
5
+ class RecordNotFound < StandardError
6
+ end
7
+
8
+ def create_record(record, &block)
9
+ type = record.class
10
+ record.id = generate_id(type)
11
+ data = NSEntityDescription.insertNewObjectForEntityForName(type.to_s, inManagedObjectContext:context(type))
12
+ record.serialize(root: false, include_id: true).each do |key, value|
13
+ data.setValue(value, forKey:key)
14
+ end
15
+ save_data_for_type(type)
16
+ load(type, record, data)
17
+ block.call(record) if block.is_a? Proc
18
+ record
19
+ end
20
+
21
+ def delete_record(record, &block)
22
+ type = record.class
23
+ data = request(type, { id: record.id }).first
24
+ if data
25
+ context(type).deleteObject(data)
26
+ save_data_for_type(type)
27
+ block.call(record) if block.is_a? Proc
28
+ record
29
+ else
30
+ raise Loco::FixtureAdapter::RecordNotFound, "#{type} with the id `#{record.id}' could not be deleted."
31
+ end
32
+ end
33
+
34
+ def find(record, id, &block)
35
+ type = record.class
36
+ data = request(type, { id: record.id }).first
37
+ if data
38
+ load(type, record, data)
39
+ block.call(record) if block.is_a? Proc
40
+ record
41
+ else
42
+ raise Loco::FixtureAdapter::RecordNotFound, "#{type} with the id `#{id}' could not be loaded."
43
+ end
44
+ end
45
+
46
+ def find_all(type, records, &block)
47
+ data = request(type)
48
+ load(type, records, data)
49
+ block.call(records) if block.is_a? Proc
50
+ records
51
+ end
52
+
53
+ def find_many(type, records, ids, &block)
54
+ data = request(type, { id: ids })
55
+ load(type, records, data)
56
+ block.call(records) if block.is_a? Proc
57
+ records
58
+ end
59
+
60
+ def find_query(type, records, query, &block)
61
+ data = request(type, query)
62
+ load(type, records, data)
63
+ block.call(records) if block.is_a? Proc
64
+ records
65
+ end
66
+
67
+ def update_record(record, &block)
68
+ type = record.class
69
+ data = request(type, { id: record.id }).first
70
+ if data
71
+ record.serialize(root: false).each do |key, value|
72
+ data.setValue(value, forKey:key)
73
+ end
74
+ save_data_for_type(type)
75
+ load(type, record, data)
76
+ block.call(record) if block.is_a? Proc
77
+ record
78
+ else
79
+ raise Loco::FixtureAdapter::RecordNotFound, "#{type} with the id `#{record.id}' could not be updated."
80
+ end
81
+ end
82
+
83
+ def serialize(record, options={})
84
+ json = {}
85
+ record.class.get_class_relationships.select{|relationship| relationship[:belongs_to] }.each do |relationship|
86
+ key = "#{relationship[:belongs_to]}_id".to_sym
87
+ json[key] = record.valueForKey(key)
88
+ end
89
+ super(record, options, json)
90
+ end
91
+
92
+ private
93
+
94
+ def request(type, query=nil)
95
+ request = NSFetchRequest.new
96
+ request.entity = entity(type)
97
+ request.sortDescriptors = [NSSortDescriptor.alloc.initWithKey('id', ascending:true)]
98
+ unless query.nil?
99
+ conditions = []
100
+ values = []
101
+ query.each do |key, value|
102
+ if value.is_a? Array
103
+ conditions << "#{key} IN %@"
104
+ else
105
+ conditions << "#{key} = %@"
106
+ end
107
+ values << value
108
+ end
109
+ request.predicate = NSPredicate.predicateWithFormat(conditions.join(' AND '), argumentArray:values)
110
+ end
111
+
112
+ error = Pointer.new(:object)
113
+ context(type).executeFetchRequest(request, error:error)
114
+ end
115
+
116
+ def context(type)
117
+ @context ||= begin
118
+ model = NSManagedObjectModel.new
119
+ model.entities = [entity(type)]
120
+
121
+ store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)
122
+ store_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), "Documents", "loco.#{type.to_s.underscore.pluralize}.sqlite"))
123
+ Loco.debug(store_url.absoluteString)
124
+ error = Pointer.new(:object)
125
+ raise "Can't add persistent SQLite store: #{error[0].description}" unless store.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:store_url, options:nil, error:error)
126
+
127
+ context = NSManagedObjectContext.new
128
+ context.persistentStoreCoordinator = store
129
+ context
130
+ end
131
+ end
132
+
133
+ def entity(type)
134
+ @entity ||= begin
135
+ entity = NSEntityDescription.new
136
+ entity.name = type.to_s
137
+
138
+ properties = type.get_class_properties.select{|prop|
139
+ prop[:type]
140
+ }.map{|prop|
141
+ property = NSAttributeDescription.new
142
+ property.name = prop[:name].to_s
143
+ case prop[:type].to_sym
144
+ when :date
145
+ property.attributeType = NSDateAttributeType
146
+ when :integer
147
+ property.attributeType = NSInteger32AttributeType
148
+ when :float
149
+ property.attributeType = NSFloatAttributeType
150
+ when :string
151
+ property.attributeType = NSStringAttributeType
152
+ end
153
+ property
154
+ }
155
+
156
+ type.get_class_relationships.select{|relationship| relationship[:belongs_to] }.each do |relationship|
157
+ property = NSAttributeDescription.new
158
+ property.name = "#{relationship[:belongs_to]}_id"
159
+ property.attributeType = NSInteger32AttributeType
160
+ properties << property
161
+ end
162
+
163
+ entity.properties = properties
164
+
165
+ entity
166
+ end
167
+ end
168
+
169
+ def generate_id(type)
170
+ key = "loco.#{type.to_s.underscore.pluralize}.last_id"
171
+ last_id = App::Persistence[key]
172
+ if last_id.nil?
173
+ last_id = 0
174
+ data = request(type)
175
+ if data.length > 0
176
+ last_id = data.sort_by{|obj| obj.valueForKey(:id).to_i }.last.id
177
+ end
178
+ end
179
+ new_id = last_id.to_i + 1
180
+ App::Persistence[key] = new_id
181
+ new_id
182
+ end
183
+
184
+ def save_data_for_type(type)
185
+ error = Pointer.new(:object)
186
+ raise "Error when saving #{type}: #{error[0].description}" unless context(type).save(error)
187
+ end
188
+
189
+ def transform_data(type, data)
190
+ if data.is_a? Array
191
+ super(type, data.map{|object|
192
+ data_item = {}
193
+
194
+ type.get_class_properties.each do |property|
195
+ key = property[:name].to_sym
196
+ data_item[key] = object.valueForKey(key)
197
+ end
198
+
199
+ type.get_class_relationships.select{|relationship| relationship[:belongs_to] }.each do |relationship|
200
+ key = "#{relationship[:belongs_to]}_id".to_sym
201
+ data_item[key] = object.valueForKey(key)
202
+ end
203
+
204
+ data_item
205
+ })
206
+ else
207
+ json = {}
208
+
209
+ type.get_class_properties.each do |property|
210
+ key = property[:name].to_sym
211
+ json[key] = data.valueForKey(key)
212
+ end
213
+
214
+ type.get_class_relationships.select{|relationship| relationship[:belongs_to] }.each do |relationship|
215
+ key = "#{relationship[:belongs_to]}_id".to_sym
216
+ json[key] = data.valueForKey(key)
217
+ end
218
+
219
+ super(type, json)
220
+ end
221
+ end
222
+
223
+ end
224
+
225
+ # Let Core Data take care of NSDate serialization
226
+ SQLiteAdapter.register_transform(:date, {
227
+ serialize: lambda{|value|
228
+ value
229
+ },
230
+ deserialize: lambda{|value|
231
+ value
232
+ }
233
+ })
234
+
235
+ end