motion-prime 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +14 -6
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +10 -8
  6. data/ROADMAP.md +9 -10
  7. data/Rakefile +1 -1
  8. data/doc/code/getting_started.rb +3 -3
  9. data/doc/code/screens.rb +25 -10
  10. data/doc/code/sections.rb +41 -0
  11. data/doc/docs/getting_started.html +8 -3
  12. data/doc/docs/screens.html +58 -19
  13. data/doc/docs/sections.html +134 -0
  14. data/files/Gemfile +7 -1
  15. data/files/Gemfile.lock +18 -9
  16. data/files/Rakefile +2 -11
  17. data/files/app/app_delegate.rb +2 -2
  18. data/files/app/config/base.rb +0 -4
  19. data/files/app/screens/application_screen.rb +2 -2
  20. data/files/app/screens/home_screen.rb +2 -0
  21. data/files/app/screens/sidebar_screen.rb +3 -3
  22. data/files/app/sections/home/section.rb +3 -0
  23. data/files/app/sections/sidebar/action.rb +1 -1
  24. data/files/app/styles/home.rb +9 -0
  25. data/files/app/styles/sidebar.rb +3 -3
  26. data/lib/motion-prime.rb +9 -0
  27. data/motion-prime/api_client.rb +5 -1
  28. data/motion-prime/app_delegate.rb +40 -45
  29. data/motion-prime/core_ext/kernel.rb +8 -0
  30. data/motion-prime/elements/_content_text_mixin.rb +3 -1
  31. data/motion-prime/elements/_text_mixin.rb +1 -1
  32. data/motion-prime/elements/base_element.rb +6 -5
  33. data/motion-prime/elements/draw/image.rb +6 -1
  34. data/motion-prime/elements/view_with_section.rb +7 -0
  35. data/motion-prime/helpers/has_style_chain_builder.rb +4 -1
  36. data/motion-prime/models/_association_mixin.rb +139 -0
  37. data/motion-prime/models/_base_mixin.rb +184 -0
  38. data/motion-prime/models/_dirty_mixin.rb +31 -0
  39. data/motion-prime/models/_finder_mixin.rb +208 -0
  40. data/motion-prime/models/{bag.rb → _nano_bag_mixin.rb} +4 -4
  41. data/motion-prime/models/{sync.rb → _sync_mixin.rb} +75 -76
  42. data/motion-prime/models/json.rb +6 -9
  43. data/motion-prime/models/model.rb +24 -172
  44. data/motion-prime/{mp.rb → prime.rb} +0 -1
  45. data/motion-prime/screens/_base_mixin.rb +9 -10
  46. data/motion-prime/screens/_navigation_mixin.rb +12 -17
  47. data/motion-prime/screens/extensions/_indicators_mixin.rb +1 -1
  48. data/motion-prime/screens/extensions/_navigation_bar_mixin.rb +3 -3
  49. data/motion-prime/screens/{base_screen.rb → screen.rb} +11 -5
  50. data/motion-prime/sections/_cell_section_mixin.rb +13 -0
  51. data/motion-prime/sections/_draw_section_mixin.rb +17 -0
  52. data/motion-prime/sections/base_section.rb +51 -16
  53. data/motion-prime/sections/form/base_field_section.rb +27 -16
  54. data/motion-prime/sections/form/base_header_section.rb +4 -3
  55. data/motion-prime/sections/form/password_field_section.rb +4 -0
  56. data/motion-prime/sections/form/select_field_section.rb +4 -0
  57. data/motion-prime/sections/form/string_field_section.rb +4 -0
  58. data/motion-prime/sections/form/submit_field_section.rb +4 -0
  59. data/motion-prime/sections/form/text_field_section.rb +4 -0
  60. data/motion-prime/sections/form.rb +7 -1
  61. data/motion-prime/sections/tabbed.rb +1 -1
  62. data/motion-prime/sections/table/table_delegate.rb +5 -0
  63. data/motion-prime/sections/table.rb +71 -51
  64. data/motion-prime/services/logger.rb +37 -0
  65. data/motion-prime/support/mp_cell_with_section.rb +9 -4
  66. data/motion-prime/support/tab_bar_controller.rb +2 -1
  67. data/motion-prime/version.rb +1 -1
  68. data/motion-prime/views/layout.rb +1 -1
  69. data/motion-prime/views/view_builder.rb +7 -0
  70. data/motion-prime.gemspec +1 -1
  71. data/spec/delegate/{base_delegate_spec.rb → delegate_spec.rb} +1 -1
  72. data/spec/helpers/{base_delegate.rb → delegates.rb} +0 -0
  73. data/spec/helpers/models.rb +11 -18
  74. data/spec/helpers/{base_screen.rb → screens.rb} +1 -1
  75. data/spec/models/{association_spec.rb → associations_spec.rb} +1 -1
  76. data/spec/models/bag_spec.rb +1 -1
  77. data/spec/models/errors_spec.rb +1 -1
  78. data/spec/models/finder_spec.rb +1 -1
  79. data/spec/models/json.rb +87 -0
  80. data/spec/models/model_spec.rb +23 -3
  81. data/spec/models/store_extension_spec.rb +1 -1
  82. data/spec/models/store_spec.rb +1 -1
  83. data/spec/screens/{base_screen_spec.rb → screen_spec.rb} +5 -1
  84. metadata +52 -43
  85. data/motion-prime/models/association.rb +0 -134
  86. data/motion-prime/models/base.rb +0 -27
  87. data/motion-prime/models/finder.rb +0 -202
  88. data/motion-prime/screens/sidebar_container_screen.rb +0 -80
@@ -0,0 +1,139 @@
1
+ module MotionPrime
2
+ module ModelAssociationMixin
3
+ extend ::MotionSupport::Concern
4
+
5
+ def _bags
6
+ @_bags ||= {}
7
+ end
8
+
9
+ def save
10
+ _bags.values.each do |bag|
11
+ bag.store = self.store
12
+ bag.save
13
+ end
14
+ super
15
+ end
16
+
17
+ module ClassMethods
18
+ # Defines bag associated with model, creates accessor for bag
19
+ #
20
+ # @param [String] name - the name of bag
21
+ # @return [Nil]
22
+ def bag(name)
23
+ define_method(name) do |*args, &block|
24
+ return _bags[name] if _bags[name]
25
+ bag_key = self.info[name]
26
+ if bag_key.present?
27
+ bag = self.class.store.bagsWithKeysInArray([bag_key]).first
28
+ end
29
+ unless bag
30
+ bag = Bag.bag
31
+ self.info[name] = bag.key
32
+ end
33
+
34
+ _bags[name] = bag
35
+ end
36
+
37
+ define_method((name + "=").to_sym) do |*args, &block|
38
+ bag = self.send(name)
39
+ case args[0]
40
+ when Bag
41
+ bag.clear
42
+ bag += args[0].saved.values
43
+ when Array
44
+ bag.clear
45
+ bag += args[0]
46
+ else
47
+ raise StoreError, "Unexpected type assigned to bags, must be an Array or MotionPrime::Bag, now: #{args[0].class}"
48
+ end
49
+ bag
50
+ end
51
+ end
52
+
53
+ # Defines has one association for model, creates accessor for association
54
+ #
55
+ # @param [String] name - the name of association
56
+ # @return [Nil]
57
+ def has_one(association_name, options = {})
58
+ bag_name = "#{association_name.pluralize}_bag"
59
+ self.bag bag_name.to_sym
60
+
61
+ self._associations ||= {}
62
+ self._associations[association_name] = options.merge(type: :one)
63
+
64
+ define_method("#{association_name}=") do |value|
65
+ self.send(bag_name).clear
66
+ self.send(:"#{bag_name}") << value
67
+ value
68
+ end
69
+ define_method("#{association_name}_attributes=") do |value|
70
+ self.send(bag_name).clear
71
+
72
+ association = association_name.classify.constantize.new
73
+ association.fetch_with_attributes(value)
74
+ association.save
75
+ self.send(:"#{bag_name}") << association
76
+ self.send(:"#{bag_name}").save
77
+ association
78
+ end
79
+ define_method("#{association_name}") do
80
+ self.send(:"#{bag_name}").to_a.first
81
+ end
82
+ end
83
+
84
+ # Defines has many association for model, creates accessor for association
85
+ #
86
+ # @param [String] name - the name of association
87
+ # @return [Nil]
88
+ def has_many(association_name, options = {})
89
+ bag_name = "#{association_name}_bag"
90
+ self.bag bag_name.to_sym
91
+
92
+ self._associations ||= {}
93
+ self._associations[association_name] = options.merge(type: :many)
94
+
95
+ define_method("#{association_name}_attributes=") do |value|
96
+ self.send(bag_name).clear
97
+
98
+ pending_save_counter = 0
99
+ collection = value.inject({}) do |result, attrs|
100
+ model = association_name.classify.constantize.new
101
+ model.fetch_with_attributes(attrs)
102
+ unique_key = model.id || "pending_#{pending_save_counter+=1}"
103
+ result.merge(unique_key => model)
104
+ end
105
+ association_data = collection.values
106
+ self.send(:"#{bag_name}=", association_data)
107
+ self.send(:"#{bag_name}").save
108
+ association_data
109
+ end
110
+ define_method("#{association_name}=") do |value|
111
+ self.send(bag_name).clear
112
+ self.send(:"#{bag_name}=", value)
113
+ end
114
+ define_method("#{association_name}") do |*args|
115
+ bag = self.send(:"#{bag_name}")
116
+ collection_options = {
117
+ association_name: association_name,
118
+ inverse_relation: {
119
+ type: :has_one,
120
+ name: self.class_name_without_kvo.demodulize.underscore,
121
+ instance: self
122
+ }
123
+ }
124
+ AssociationCollection.new(bag, collection_options, *args)
125
+ end
126
+ end
127
+
128
+ def belongs_to(association_name, options = {})
129
+ self._associations ||= {}
130
+ self._associations[association_name] = {
131
+ type: :belongs_to_one,
132
+ class_name: association_name.classify
133
+ }.merge(options)
134
+
135
+ self.send(:attr_accessor, association_name)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,184 @@
1
+ module MotionPrime
2
+ module ModelBaseMixin
3
+ extend ::MotionSupport::Concern
4
+
5
+ def self.included(base)
6
+ base.class_attribute :default_sort_options
7
+ end
8
+
9
+ def save
10
+ raise StoreError, 'No store provided' unless self.store
11
+ error_ptr = Pointer.new(:id)
12
+ self.store.addObject(self, error: error_ptr)
13
+ raise StoreError, error_ptr[0].description if error_ptr[0]
14
+ self
15
+ end
16
+
17
+ def delete
18
+ raise StoreError, 'No store provided' unless self.store
19
+
20
+ error_ptr = Pointer.new(:id)
21
+ self.store.removeObject(self, error: error_ptr)
22
+ raise StoreError, error_ptr[0].description if error_ptr[0]
23
+ self
24
+ end
25
+
26
+ def store
27
+ super || self.class.store
28
+ end
29
+
30
+ def assign_attributes(new_attributes, options = {})
31
+ attributes = new_attributes.symbolize_keys
32
+ attributes.each do |k, v|
33
+ if has_attribute?(k)
34
+ assign_attribute(k, v) unless options[:skip_nil_values] && v.nil?
35
+ elsif options[:validate_attribute_presence]
36
+ raise(StoreError, "unknown attribute: '#{k}'")
37
+ else
38
+ NSLog("unknown attribute: #{k}")
39
+ end
40
+ end
41
+ end
42
+
43
+ def assign_attribute(name, value)
44
+ self.send("#{name}=", value) if has_attribute?(name)
45
+ end
46
+
47
+ def has_attribute?(name)
48
+ respond_to?("#{name}=")
49
+ end
50
+
51
+ def attributes_hash
52
+ self.info.to_hash.symbolize_keys
53
+ end
54
+
55
+ def new_record?
56
+ id.blank?
57
+ end
58
+
59
+ def persisted?
60
+ !new_record?
61
+ end
62
+
63
+ def model_name
64
+ self.class_name_without_kvo.underscore
65
+ end
66
+
67
+ def inspect
68
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}> " + MotionPrime::JSON.generate(info)
69
+ end
70
+
71
+ module ClassMethods
72
+ # Initialize a new object
73
+ #
74
+ # Examples:
75
+ # User.new(name: "Bob", age: 10)
76
+ #
77
+ # @return MotionPrime::Model unsaved model
78
+ def new(data = {}, options = {})
79
+ data.keys.each do |key|
80
+ unless self.attributes.member? key.to_sym
81
+ if options[:validate_attribute_presence]
82
+ raise StoreError, "unknown attribute: '#{key}'"
83
+ else
84
+ data.delete(key)
85
+ end
86
+ end
87
+ end
88
+
89
+ object = self.nanoObjectWithDictionary(data)
90
+ object
91
+ end
92
+
93
+ # Initialize a new object and save it
94
+ #
95
+ # Examples:
96
+ # User.create(name: "Bob", age: 10)
97
+ #
98
+ # @return MotionPrime::Model saved model
99
+ def create(data = {})
100
+ object = self.new(data)
101
+ object.save
102
+ end
103
+
104
+ # Define model attribute
105
+ #
106
+ # Examples:
107
+ # class User < MotionPrime::Model
108
+ # attribute :name
109
+ # attribute :age
110
+ # end
111
+ #
112
+ # @return Nil
113
+ def attribute(name, options = {})
114
+ attributes << name
115
+
116
+ define_method(name) do |*args, &block|
117
+ self.info[name]
118
+ end
119
+
120
+ define_method((name + "=").to_sym) do |*args, &block|
121
+ value = args[0]
122
+ case options[:type].to_s
123
+ when 'integer' then value = value.to_i
124
+ when 'float' then value = value.to_f
125
+ end unless value.nil?
126
+
127
+ self.info[name] = value
128
+ end
129
+
130
+ if options[:type].to_s == 'boolean'
131
+ define_method("#{name}?") do
132
+ !!self.info[name]
133
+ end
134
+ end
135
+ end
136
+
137
+ # Set or return all model attribute names
138
+ #
139
+ # @return Array array of attribute names
140
+ def attributes(*attrs)
141
+ if attrs.size > 0
142
+ attrs.each{|attr| attribute attr}
143
+ else
144
+ @attributes ||= []
145
+ end
146
+ end
147
+
148
+ # Return store associated with model class, or shared store by default
149
+ #
150
+ # @return MotionPrime::Store store
151
+ def store
152
+ @store ||= MotionPrime::Store.shared_store
153
+ end
154
+
155
+ # Define store associated with model class
156
+ #
157
+ # @param MotionPrime::Store store
158
+ # @return MotionPrime::Store store
159
+ def store=(store)
160
+ @store = store
161
+ end
162
+
163
+ # Count of models
164
+ #
165
+ # @return Fixnum count
166
+ def count
167
+ self.store.count(self)
168
+ end
169
+
170
+ # Delete objects from store
171
+ #
172
+ # @param [Array, MotionPrime::Model] objects to delete
173
+ # @return [Array] result
174
+ def delete(*args)
175
+ keys = find_keys(*args)
176
+ self.store.delete_keys(keys)
177
+ end
178
+
179
+ def default_sort(sort_options)
180
+ self.default_sort_options = sort_options
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,31 @@
1
+ module MotionPrime
2
+ module ModelDirtyMixin
3
+ extend ::MotionSupport::Concern
4
+
5
+ def self.included(base)
6
+ base.class_attribute :_changed_attributes
7
+ end
8
+
9
+ def track_changed_attributes(&block)
10
+ @_changed_attributes ||= {}
11
+ old_attrs = self.info.clone
12
+ result = block.call
13
+ new_attrs = self.info.clone
14
+ new_attrs.each do |key, value|
15
+ if value != old_attrs[key] && ! @_changed_attributes.has_key?(key.to_s)
16
+ @_changed_attributes[key.to_s] = old_attrs[key]
17
+ end
18
+ end
19
+ result
20
+ end
21
+
22
+ def has_changed?(key = nil)
23
+ @_changed_attributes ||= {}
24
+ if key
25
+ @_changed_attributes.has_key?(key.to_s)
26
+ else
27
+ @_changed_attributes.present?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,208 @@
1
+ module MotionPrime
2
+ module ModelFinderMixin
3
+ extend ::MotionSupport::Concern
4
+
5
+ module ClassMethods
6
+ attr_accessor :bare_class
7
+
8
+ # Find all models
9
+ #
10
+ # @return [Array] array of models
11
+ def all(*args)
12
+ return [] unless self.store
13
+
14
+ if args[0].is_a?(Hash)
15
+ sort_options = args[0][:sort] || {}
16
+ else
17
+ sort_options = {}
18
+ end
19
+
20
+ if sort_options.empty?
21
+ self.store.objectsOfClassNamed(self.bare_class_name)
22
+ else
23
+ sort_descriptors = sort_descriptor_with_options(sort_options)
24
+ self.store.objectsOfClassNamed(self.bare_class_name, usingSortDescriptors:sort_descriptors)
25
+ end
26
+ end
27
+
28
+ # Find last model model by default order
29
+ def last
30
+ all.last
31
+ end
32
+
33
+ # Find first model model by default order
34
+ def first
35
+ all.first
36
+ end
37
+
38
+ # Find model by criteria
39
+ #
40
+ # Examples:
41
+ # User.find(:name, NSFEqualTo, "Bob") # => [<User#1>]
42
+ # User.find(:name => "Bob") # => [<User#1>]
43
+ # User.find(:name => {NSFEqualTo => "Bob"}) # => [<User#1>]
44
+ #
45
+ # @return [Array] array of models
46
+ def find(*arg)
47
+ if arg[0].is_a?(Hash)
48
+ # hash style
49
+ options = arg[0]
50
+ if arg[1] && arg[1].is_a?(Hash)
51
+ sort_options = arg[1][:sort] || {}
52
+ else
53
+ sort_options = {}
54
+ end
55
+ elsif arg[0] && arg[1] && arg[2]
56
+ # standard way to find
57
+ options = {arg[0] => {arg[1] => arg[2]}}
58
+ if arg[4] && arg[4].is_a?(Hash)
59
+ sort_options = arg[4][:sort] || {}
60
+ else
61
+ sort_options = {}
62
+ end
63
+ elsif arg.empty?
64
+ options = {}
65
+ sort_options = {}
66
+ else
67
+ raise "unexpected parameters #{arg}"
68
+ end
69
+ search = NSFNanoSearch.searchWithStore(self.store)
70
+
71
+ expressions = expressions_with_options(options)
72
+ search.expressions = expressions
73
+
74
+ sort_descriptors = sort_descriptor_with_options(sort_options)
75
+ search.sort = sort_descriptors
76
+ search.filterClass = self.bare_class_name
77
+
78
+ error_ptr = Pointer.new(:id)
79
+ searchResults = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr)
80
+ raise StoreError, error_ptr[0].description if error_ptr[0]
81
+
82
+ if searchResults.is_a?(NSDictionary)
83
+ searchResults.values
84
+ else
85
+ searchResults
86
+ end
87
+ end
88
+
89
+ # Find model keys by criteria
90
+ #
91
+ # Examples:
92
+ # User.find_keys(:name, NSFEqualTo, "Bob") # => ["1"]
93
+ # User.find_keys(:name => "Bob") # => ["1"]
94
+ # User.find_keys(:name => {NSFEqualTo => "Bob"}) # => ["1"]
95
+ #
96
+ # @return [Array] array of models
97
+ def find_keys(*arg)
98
+ if arg[0].is_a?(Hash)
99
+ # hash style
100
+ options = arg[0]
101
+ if arg[1] && arg[1].is_a?(Hash)
102
+ sort_options = arg[1][:sort] || {}
103
+ else
104
+ sort_options = {}
105
+ end
106
+ elsif arg[0] && arg[1] && arg[2]
107
+ # standard way to find
108
+ options = {arg[0] => {arg[1] => arg[2]}}
109
+ if arg[4] && arg[4].is_a?(Hash)
110
+ sort_options = arg[4][:sort] || {}
111
+ else
112
+ sort_options = {}
113
+ end
114
+ elsif arg.empty?
115
+ options = {}
116
+ sort_options = {}
117
+ else
118
+ raise "unexpected parameters #{arg}"
119
+ end
120
+
121
+ search = NSFNanoSearch.searchWithStore(self.store)
122
+
123
+ expressions = expressions_with_options(options)
124
+ search.expressions = expressions
125
+
126
+ sort_descriptors = sort_descriptor_with_options(sort_options)
127
+ search.sort = sort_descriptors
128
+ search.filterClass = self.bare_class_name
129
+
130
+ error_ptr = Pointer.new(:id)
131
+ searchResults = search.searchObjectsWithReturnType(NSFReturnKeys, error:error_ptr)
132
+ raise StoreError, error_ptr[0].description if error_ptr[0]
133
+
134
+ if searchResults.is_a?(NSDictionary)
135
+ searchResults.values
136
+ else
137
+ searchResults
138
+ end
139
+ end
140
+
141
+ # Find a model by key
142
+ #
143
+ # Examples:
144
+ # User.find_by_key(my_key)
145
+ #
146
+ # @return [Object, Nil] an object or nil (if not found)
147
+ def find_by_key(key)
148
+ search = NSFNanoSearch.searchWithStore(self.store)
149
+ search.key = key
150
+
151
+ error_ptr = Pointer.new(:id)
152
+ searchResult = search.searchObjectsWithReturnType(NSFReturnObjects, error:error_ptr).first
153
+ raise StoreError, error_ptr[0].description if error_ptr[0]
154
+
155
+ searchResult.last if searchResult
156
+ end
157
+
158
+ def bare_class_name
159
+ subject = @bare_class || self
160
+ subject.to_s.split("::").last
161
+ end
162
+
163
+ private
164
+ def expressions_with_options(options)
165
+ expressions = []
166
+
167
+ options.each do |key, val|
168
+ attribute = NSFNanoPredicate.predicateWithColumn(NSFAttributeColumn, matching:NSFEqualTo, value:key.to_s)
169
+ expression = NSFNanoExpression.expressionWithPredicate(attribute)
170
+ if val.is_a?(Hash)
171
+ val.each do |operator, sub_val|
172
+ value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:operator, value:sub_val.to_s)
173
+ expression.addPredicate(value, withOperator:NSFAnd)
174
+ end
175
+ elsif val.is_a?(Array)
176
+ value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.pop)
177
+ expression.addPredicate(value, withOperator:NSFAnd)
178
+
179
+ val.each do |sub_val|
180
+ value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:sub_val.to_s)
181
+ expression.addPredicate(value, withOperator:NSFOr)
182
+ end
183
+ else
184
+ value = NSFNanoPredicate.predicateWithColumn(NSFValueColumn, matching:NSFEqualTo, value:val.to_s)
185
+ expression.addPredicate(value, withOperator:NSFAnd)
186
+ end
187
+ expressions << expression
188
+ end
189
+ return expressions
190
+ end
191
+
192
+ SORT_MAPPING = {
193
+ 'asc' => true,
194
+ 'desc' => false,
195
+ }
196
+
197
+ def sort_descriptor_with_options(options)
198
+ sorter = options.collect do |opt_key, opt_val|
199
+ if SORT_MAPPING.keys.include?(opt_val.to_s.downcase)
200
+ NSFNanoSortDescriptor.alloc.initWithAttribute(opt_key.to_s, ascending:SORT_MAPPING[opt_val.to_s.downcase])
201
+ else
202
+ raise "unsupported sort parameters: #{opt_val}"
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -1,6 +1,6 @@
1
- motion_require './finder.rb'
1
+ motion_require './_finder_mixin.rb'
2
2
  module MotionPrime
3
- module BagInstanceMethods
3
+ module NanoBagMixin
4
4
  def self.included(base)
5
5
  base.class_eval do
6
6
  alias_method :saved, :savedObjects
@@ -131,6 +131,6 @@ module MotionPrime
131
131
  end
132
132
 
133
133
  class NSFNanoBag
134
- include MotionPrime::ModelFinderMethods
135
- include MotionPrime::BagInstanceMethods
134
+ include MotionPrime::ModelFinderMixin::ClassMethods
135
+ include MotionPrime::NanoBagMixin
136
136
  end