active_remote 1.7.1 → 1.8.0.rc1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e0a0b815dc24d0ba8c884b135055cc9fb42fbc0
4
- data.tar.gz: 59144b3bb85ecebf7e660e0b5a93a27db60f7751
3
+ metadata.gz: f15daf36d3c8b8835e43b8fdbb36558e9272bf62
4
+ data.tar.gz: ac509c0f6b2a10444c9b7fe1c8fc6a29dec7350e
5
5
  SHA512:
6
- metadata.gz: ac81f4a6f6f7206306e2712a0fb5fe6d90f72228ce63667f480f609d32243e3bee20ad3465cd2766893994ee01c34733a411c9ee5e047cb89781de47d4262d78
7
- data.tar.gz: 471a29c7b76006c8d8fd86835196a3ad5dbbbe76c419c9e5cca6d7f1b86e195fb9b2b149185348fa34df1e8875aba8260a9821838e689ced9d75c9b2b0cd3062
6
+ metadata.gz: 30d4f0bef9362330f7a52adf478766d4a4c9a24c4a9b1e92c359d12f4824444b4d4a55b9a78e85fbfc70f448086dd366467dba82952d99728e552c1635e7d13b
7
+ data.tar.gz: 3f65730eb838053732644492642d4bd32d69d82cfe1be1452b75be61c08ca8f95212832364844d1b31c9b3acc885f7d09c2fcbfb0aeb5f4be873ef9bd01b767f
@@ -39,9 +39,12 @@ module ActiveRemote
39
39
  #
40
40
  def belongs_to(belongs_to_klass, options={})
41
41
  perform_association(belongs_to_klass, options) do |klass, object|
42
- foreign_key = options.fetch(:foreign_key) { :"#{belongs_to_klass}_guid" }
43
- association_guid = object.read_attribute(foreign_key)
44
- klass.search(:guid => association_guid).first if association_guid
42
+ foreign_key = options.fetch(:foreign_key) { :"#{klass.name.demodulize.underscore}_guid" }
43
+ search_hash = {}
44
+ search_hash[:guid] = object.read_attribute(foreign_key)
45
+ search_hash[options[:scope]] = object.read_attribute(options[:scope]) if options.has_key?(:scope)
46
+
47
+ search_hash.values.any?(&:nil?) ? nil : klass.search(search_hash).first
45
48
  end
46
49
  end
47
50
 
@@ -76,9 +79,13 @@ module ActiveRemote
76
79
  # end
77
80
  #
78
81
  def has_many(has_many_class, options={})
79
- perform_association( has_many_class, options ) do |klass, object|
82
+ perform_association(has_many_class, options) do |klass, object|
80
83
  foreign_key = options.fetch(:foreign_key) { :"#{object.class.name.demodulize.underscore}_guid" }
81
- object.guid ? klass.search(foreign_key => object.guid) : []
84
+ search_hash = {}
85
+ search_hash[foreign_key] = object.guid
86
+ search_hash[options[:scope]] = object.read_attribute(options[:scope]) if options.has_key?(:scope)
87
+
88
+ search_hash.values.any?(&:nil?) ? [] : klass.search(search_hash)
82
89
  end
83
90
  end
84
91
 
@@ -99,8 +106,6 @@ module ActiveRemote
99
106
  #
100
107
  # class User
101
108
  # has_one :client
102
- # end
103
- #
104
109
  # An equivalent code snippet without a `has_one` declaration would be:
105
110
  #
106
111
  # ====Examples
@@ -114,19 +119,34 @@ module ActiveRemote
114
119
  def has_one(has_one_klass, options={})
115
120
  perform_association(has_one_klass, options) do |klass, object|
116
121
  foreign_key = options.fetch(:foreign_key) { :"#{object.class.name.demodulize.underscore}_guid" }
117
- klass.search(foreign_key => object.guid).first if object.guid
122
+ search_hash = {}
123
+ search_hash[foreign_key] = object.guid
124
+ search_hash[options[:scope]] = object.read_attribute(options[:scope]) if options.has_key?(:scope)
125
+
126
+ search_hash.values.any?(&:nil?) ? nil : klass.search(search_hash).first
118
127
  end
119
128
  end
120
129
 
130
+ # when requiring an attribute on your search, we verify the attribute
131
+ # exists on both models
132
+ def validate_scoped_attributes(associated_class, object_class, options)
133
+ raise "Could not find attribute: '#{options[:scope]}' on #{object_class}" unless object_class.public_instance_methods.include?(options[:scope])
134
+ raise "Could not find attribute: '#{options[:scope]}' on #{associated_class}" unless associated_class.public_instance_methods.include?(options[:scope])
135
+ end
136
+
121
137
  private
122
138
 
123
- def perform_association(associated_klass, optionz={})
139
+ def perform_association(associated_klass, options={})
140
+
124
141
  define_method(associated_klass) do
142
+ klass_name = options.fetch(:class_name){ associated_klass }
143
+ klass = klass_name.to_s.classify.constantize
144
+
145
+ self.class.validate_scoped_attributes(klass, self.class, options) if options.has_key?(:scope)
146
+
125
147
  value = instance_variable_get(:"@#{associated_klass}")
126
148
 
127
149
  unless value
128
- klass_name = optionz.fetch(:class_name){ associated_klass }
129
- klass = klass_name.to_s.classify.constantize
130
150
  value = yield( klass, self )
131
151
  instance_variable_set(:"@#{associated_klass}", value)
132
152
  end
@@ -134,6 +154,7 @@ module ActiveRemote
134
154
  return value
135
155
  end
136
156
  end
157
+
137
158
  end
138
159
  end
139
160
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveRemote
2
- VERSION = "1.7.1"
2
+ VERSION = "1.8.0.rc1"
3
3
  end
@@ -7,8 +7,9 @@ describe ActiveRemote::Association do
7
7
  describe ".belongs_to" do
8
8
  context "simple association" do
9
9
  let(:author_guid) { "AUT-123" }
10
-
11
- subject { Post.new(:author_guid => author_guid) }
10
+ let(:user_guid) { "USR-123" }
11
+ let(:default_category_guid) { "CAT-123" }
12
+ subject { Post.new(:author_guid => author_guid, :user_guid => user_guid) }
12
13
 
13
14
  it { should respond_to(:author) }
14
15
 
@@ -36,6 +37,32 @@ describe ActiveRemote::Association do
36
37
  subject.author.should be_nil
37
38
  end
38
39
  end
40
+
41
+ context 'scoped field' do
42
+ it { should respond_to(:user) }
43
+
44
+ it "searches the associated model for multiple records" do
45
+ Author.should_receive(:search).with(:guid => subject.author_guid, :user_guid => subject.user_guid).and_return(records)
46
+ subject.user.should eq(record)
47
+ end
48
+
49
+ context 'when user_guid doesnt exist on model 'do
50
+ before { subject.stub(:respond_to?).with("user_guid").and_return(false) }
51
+
52
+ it 'raises an error' do
53
+ expect {subject.user}.to raise_error
54
+ end
55
+ end
56
+
57
+ context 'when user_guid doesnt exist on associated model 'do
58
+ before { Author.stub_chain(:public_instance_methods, :include?).with(:user_guid).and_return(false) }
59
+
60
+ it 'raises an error' do
61
+ expect {subject.user}.to raise_error
62
+ end
63
+ end
64
+ end
65
+
39
66
  end
40
67
 
41
68
  context "specific association with class name" do
@@ -46,19 +73,19 @@ describe ActiveRemote::Association do
46
73
 
47
74
  it "searches the associated model for a single record" do
48
75
  Author.should_receive(:search).with(:guid => subject.author_guid).and_return(records)
49
- subject.author.should eq record
76
+ subject.coauthor.should eq record
50
77
  end
51
78
  end
52
79
 
53
80
  context "specific association with class name and foreign_key" do
54
81
  let(:author_guid) { "AUT-456" }
55
82
 
56
- subject { Post.new(:author_guid => author_guid) }
83
+ subject { Post.new(:bestseller_guid => author_guid) }
57
84
  it { should respond_to(:bestseller) }
58
85
 
59
86
  it "searches the associated model for a single record" do
60
87
  Author.should_receive(:search).with(:guid => subject.bestseller_guid).and_return(records)
61
- subject.author.should eq record
88
+ subject.bestseller.should eq record
62
89
  end
63
90
  end
64
91
  end
@@ -66,8 +93,9 @@ describe ActiveRemote::Association do
66
93
  describe ".has_many" do
67
94
  let(:records) { [ record, record, record ] }
68
95
  let(:guid) { "AUT-123" }
96
+ let(:user_guid) { "USR-123" }
69
97
 
70
- subject { Author.new(:guid => guid) }
98
+ subject { Author.new(:guid => guid, :user_guid => user_guid) }
71
99
 
72
100
  it { should respond_to(:posts) }
73
101
 
@@ -113,55 +141,112 @@ describe ActiveRemote::Association do
113
141
  subject.bestseller_posts.should eq(records)
114
142
  end
115
143
  end
144
+
145
+ context 'scoped field' do
146
+ it { should respond_to(:user_posts) }
147
+
148
+ it "searches the associated model for multiple records" do
149
+ Post.should_receive(:search).with(:author_guid => subject.guid, :user_guid => subject.user_guid).and_return(records)
150
+ subject.user_posts.should eq(records)
151
+ end
152
+
153
+ context 'when user_guid doesnt exist on model 'do
154
+ before { subject.stub(:respond_to?).with("user_guid").and_return(false) }
155
+
156
+ it 'raises an error' do
157
+ expect {subject.user_posts}.to raise_error
158
+ end
159
+ end
160
+
161
+ context 'when user_guid doesnt exist on associated model 'do
162
+ before { Post.stub_chain(:public_instance_methods, :include?).with(:user_guid).and_return(false) }
163
+
164
+ it 'raises an error' do
165
+ expect {subject.user_posts}.to raise_error
166
+ end
167
+ end
168
+ end
116
169
  end
117
170
 
118
171
  describe ".has_one" do
119
- let(:guid) { "PST-123" }
172
+ let(:guid) { "CAT-123" }
173
+ let(:user_guid) { "USR-123" }
174
+ let(:category_attributes) {
175
+ {
176
+ :guid => guid,
177
+ :user_guid => user_guid
178
+ }
179
+ }
120
180
 
121
- subject { Post.new(:guid => guid) }
181
+ subject { Category.new(category_attributes) }
122
182
 
123
- it { should respond_to(:category) }
183
+ it { should respond_to(:author) }
124
184
 
125
185
  it "searches the associated model for all associated records" do
126
- Category.should_receive(:search).with(:post_guid => subject.guid).and_return(records)
127
- subject.category.should eq record
186
+ Author.should_receive(:search).with(:category_guid => subject.guid).and_return(records)
187
+ subject.author.should eq record
128
188
  end
129
189
 
130
190
  it "memoizes the result record" do
131
- Category.should_receive(:search).once.with(:post_guid => subject.guid).and_return(records)
132
- 3.times { subject.category.should eq record }
191
+ Author.should_receive(:search).once.with(:category_guid => subject.guid).and_return(records)
192
+ 3.times { subject.author.should eq record }
133
193
  end
134
194
 
135
195
  context "when guid is nil" do
136
- subject { Post.new }
196
+ subject { Category.new }
137
197
 
138
198
  it "returns nil" do
139
- subject.category.should be_nil
199
+ subject.author.should be_nil
140
200
  end
141
201
  end
142
202
 
143
203
  context "when the search is empty" do
144
204
  it "returns a nil value" do
145
- Category.should_receive(:search).with(:post_guid => subject.guid).and_return([])
146
- subject.category.should be_nil
205
+ Author.should_receive(:search).with(:category_guid => subject.guid).and_return([])
206
+ subject.author.should be_nil
147
207
  end
148
208
  end
149
209
 
150
210
  context "specific association with class name" do
151
- it { should respond_to(:main_category) }
211
+ it { should respond_to(:senior_author) }
152
212
 
153
213
  it "searches the associated model for a single record" do
154
- Category.should_receive(:search).with(:post_guid => subject.guid).and_return(records)
155
- subject.main_category.should eq record
214
+ Author.should_receive(:search).with(:category_guid => subject.guid).and_return(records)
215
+ subject.senior_author.should eq record
156
216
  end
157
217
  end
158
218
 
159
219
  context "specific association with class name and foreign_key" do
160
- it { should respond_to(:default_category) }
220
+ it { should respond_to(:primary_editor) }
161
221
 
162
222
  it "searches the associated model for a single record" do
163
- Category.should_receive(:search).with(:template_post_guid => subject.guid).and_return(records)
164
- subject.default_category.should eq record
223
+ Author.should_receive(:search).with(:editor_guid => subject.guid).and_return(records)
224
+ subject.primary_editor.should eq record
225
+ end
226
+ end
227
+
228
+ context 'scoped field' do
229
+ it { should respond_to(:chief_editor) }
230
+
231
+ it "searches the associated model for multiple records" do
232
+ Author.should_receive(:search).with(:chief_editor_guid => subject.guid, :user_guid => subject.user_guid).and_return(records)
233
+ subject.chief_editor.should eq(record)
234
+ end
235
+
236
+ context 'when user_guid doesnt exist on model 'do
237
+ before { subject.stub(:respond_to?).with("user_guid").and_return(false) }
238
+
239
+ it 'raises an error' do
240
+ expect {subject.chief_editor}.to raise_error
241
+ end
242
+ end
243
+
244
+ context 'when user_guid doesnt exist on associated model 'do
245
+ before { Author.stub_chain(:public_instance_methods, :include?).with(:user_guid).and_return(false) }
246
+
247
+ it 'raises an error' do
248
+ expect {subject.chief_editor}.to raise_error
249
+ end
165
250
  end
166
251
  end
167
252
  end
@@ -6,6 +6,7 @@ message Author {
6
6
  optional string guid = 1;
7
7
  optional string name = 2;
8
8
  repeated Error errors = 3;
9
+ optional string user_guid = 4;
9
10
  }
10
11
 
11
12
  message Authors {
@@ -3,12 +3,13 @@ package generic.remote;
3
3
  import "support/protobuf/error.proto";
4
4
  import "support/protobuf/category.proto";
5
5
 
6
- message Post {
6
+ message Post {
7
7
  optional string guid = 1;
8
8
  optional string name = 2;
9
9
  optional string author_guid = 3;
10
10
  optional Category category = 4;
11
11
  repeated Error errors = 5;
12
+ optional string user_guid = 6;
12
13
  }
13
14
 
14
15
  message Posts {
@@ -19,6 +20,7 @@ message PostRequest {
19
20
  repeated string guid = 1;
20
21
  repeated string name = 2;
21
22
  repeated string author_guid = 3;
23
+ repeated string user_guid = 4;
22
24
  }
23
25
 
24
26
  service PostService {
@@ -2,10 +2,14 @@ package generic.remote;
2
2
 
3
3
  import "support/protobuf/error.proto";
4
4
 
5
- message Category {
5
+ message Category {
6
6
  optional string guid = 1;
7
7
  optional string name = 2;
8
8
  repeated Error errors = 3;
9
+ optional string user_guid = 4;
10
+ optional string author_guid = 4;
11
+ optional string chief_editor_guid = 4;
12
+ optional string editor_guid = 4;
9
13
  }
10
14
 
11
15
  message Categories {
@@ -8,9 +8,15 @@ class Author < ::ActiveRemote::Base
8
8
 
9
9
  attribute :guid
10
10
  attribute :name
11
+ attribute :user_guid
12
+ attribute :chief_editor_guid
13
+ attribute :editor_guid
14
+ attribute :category_guid
11
15
 
12
16
  has_many :posts
17
+ has_many :user_posts, :class_name => "::Post", :scope => :user_guid
13
18
  has_many :flagged_posts, :class_name => "::Post"
14
19
  has_many :bestseller_posts, :class_name => "::Post", :foreign_key => :bestseller_guid
15
20
 
21
+ belongs_to :category
16
22
  end
@@ -7,10 +7,14 @@ class Category < ::ActiveRemote::Base
7
7
  service_class ::Generic::Remote::CategoryService
8
8
 
9
9
  attribute :guid
10
- attribute :name
11
- attribute :post_id
10
+ attribute :user_guid
11
+ attribute :chief_editor_guid
12
12
 
13
- belongs_to :post
13
+ has_many :posts
14
+
15
+ has_one :author
16
+ has_one :senior_author, :class_name => "::Author"
17
+ has_one :primary_editor, :class_name => "::Author", :foreign_key => :editor_guid
18
+ has_one :chief_editor, :class_name => "::Author", :scope => :user_guid, :foreign_key => :chief_editor_guid
14
19
 
15
- alias_method :template_post_guid, :post_id
16
20
  end
@@ -9,13 +9,11 @@ class Post < ::ActiveRemote::Base
9
9
  attribute :guid
10
10
  attribute :name
11
11
  attribute :author_guid
12
+ attribute :user_guid
13
+ attribute :bestseller_guid
12
14
 
13
15
  belongs_to :author
14
16
  belongs_to :coauthor, :class_name => '::Author'
15
17
  belongs_to :bestseller, :class_name => '::Author', :foreign_key => :bestseller_guid
16
- has_one :category
17
- has_one :main_category, :class_name => '::Category'
18
- has_one :default_category, :class_name => '::Category', :foreign_key => :template_post_guid
19
-
20
- alias_method :bestseller_guid, :author_guid
18
+ belongs_to :user, :class_name => '::Author', :scope => :user_guid
21
19
  end
@@ -4,6 +4,7 @@
4
4
  require 'protobuf/message'
5
5
  require 'protobuf/rpc/service'
6
6
 
7
+
7
8
  ##
8
9
  # Imports
9
10
  #
@@ -11,14 +12,15 @@ require 'support/protobuf/error.pb'
11
12
 
12
13
  module Generic
13
14
  module Remote
14
-
15
+
15
16
  ##
16
17
  # Message Classes
17
18
  #
18
19
  class Author < ::Protobuf::Message; end
19
20
  class Authors < ::Protobuf::Message; end
20
21
  class AuthorRequest < ::Protobuf::Message; end
21
-
22
+
23
+
22
24
  ##
23
25
  # Message Fields
24
26
  #
@@ -26,19 +28,21 @@ module Generic
26
28
  optional ::Protobuf::Field::StringField, :guid, 1
27
29
  optional ::Protobuf::Field::StringField, :name, 2
28
30
  repeated ::Generic::Error, :errors, 3
31
+ optional ::Protobuf::Field::StringField, :user_guid, 4
29
32
  end
30
-
33
+
31
34
  class Authors
32
35
  repeated ::Generic::Remote::Author, :records, 1
33
36
  end
34
-
37
+
35
38
  class AuthorRequest
36
39
  repeated ::Protobuf::Field::StringField, :guid, 1
37
40
  repeated ::Protobuf::Field::StringField, :name, 2
38
41
  end
39
-
42
+
43
+
40
44
  ##
41
- # Services
45
+ # Service Classes
42
46
  #
43
47
  class AuthorService < ::Protobuf::Rpc::Service
44
48
  rpc :search, ::Generic::Remote::AuthorRequest, ::Generic::Remote::Authors
@@ -50,5 +54,8 @@ module Generic
50
54
  rpc :delete_all, ::Generic::Remote::Authors, ::Generic::Remote::Authors
51
55
  rpc :destroy_all, ::Generic::Remote::Authors, ::Generic::Remote::Authors
52
56
  end
57
+
53
58
  end
59
+
54
60
  end
61
+
@@ -4,6 +4,7 @@
4
4
  require 'protobuf/message'
5
5
  require 'protobuf/rpc/service'
6
6
 
7
+
7
8
  ##
8
9
  # Imports
9
10
  #
@@ -12,14 +13,15 @@ require 'support/protobuf/category.pb'
12
13
 
13
14
  module Generic
14
15
  module Remote
15
-
16
+
16
17
  ##
17
18
  # Message Classes
18
19
  #
19
20
  class Post < ::Protobuf::Message; end
20
21
  class Posts < ::Protobuf::Message; end
21
22
  class PostRequest < ::Protobuf::Message; end
22
-
23
+
24
+
23
25
  ##
24
26
  # Message Fields
25
27
  #
@@ -29,20 +31,23 @@ module Generic
29
31
  optional ::Protobuf::Field::StringField, :author_guid, 3
30
32
  optional ::Generic::Remote::Category, :category, 4
31
33
  repeated ::Generic::Error, :errors, 5
34
+ optional ::Protobuf::Field::StringField, :user_guid, 6
32
35
  end
33
-
36
+
34
37
  class Posts
35
38
  repeated ::Generic::Remote::Post, :records, 1
36
39
  end
37
-
40
+
38
41
  class PostRequest
39
42
  repeated ::Protobuf::Field::StringField, :guid, 1
40
43
  repeated ::Protobuf::Field::StringField, :name, 2
41
44
  repeated ::Protobuf::Field::StringField, :author_guid, 3
45
+ repeated ::Protobuf::Field::StringField, :user_guid, 4
42
46
  end
43
-
47
+
48
+
44
49
  ##
45
- # Services
50
+ # Service Classes
46
51
  #
47
52
  class PostService < ::Protobuf::Rpc::Service
48
53
  rpc :search, ::Generic::Remote::PostRequest, ::Generic::Remote::Posts
@@ -54,5 +59,8 @@ module Generic
54
59
  rpc :delete_all, ::Generic::Remote::Posts, ::Generic::Remote::Posts
55
60
  rpc :destroy_all, ::Generic::Remote::Posts, ::Generic::Remote::Posts
56
61
  end
62
+
57
63
  end
64
+
58
65
  end
66
+
@@ -4,6 +4,7 @@
4
4
  require 'protobuf/message'
5
5
  require 'protobuf/rpc/service'
6
6
 
7
+
7
8
  ##
8
9
  # Imports
9
10
  #
@@ -11,14 +12,15 @@ require 'support/protobuf/error.pb'
11
12
 
12
13
  module Generic
13
14
  module Remote
14
-
15
+
15
16
  ##
16
17
  # Message Classes
17
18
  #
18
19
  class Tag < ::Protobuf::Message; end
19
20
  class Tags < ::Protobuf::Message; end
20
21
  class TagRequest < ::Protobuf::Message; end
21
-
22
+
23
+
22
24
  ##
23
25
  # Message Fields
24
26
  #
@@ -27,18 +29,19 @@ module Generic
27
29
  optional ::Protobuf::Field::StringField, :name, 2
28
30
  repeated ::Generic::Error, :errors, 3
29
31
  end
30
-
32
+
31
33
  class Tags
32
34
  repeated ::Generic::Remote::Tag, :records, 1
33
35
  end
34
-
36
+
35
37
  class TagRequest
36
38
  repeated ::Protobuf::Field::StringField, :guid, 1
37
39
  repeated ::Protobuf::Field::StringField, :name, 2
38
40
  end
39
-
41
+
42
+
40
43
  ##
41
- # Services
44
+ # Service Classes
42
45
  #
43
46
  class TagService < ::Protobuf::Rpc::Service
44
47
  rpc :search, ::Generic::Remote::TagRequest, ::Generic::Remote::Tags
@@ -50,5 +53,8 @@ module Generic
50
53
  rpc :delete_all, ::Generic::Remote::Tags, ::Generic::Remote::Tags
51
54
  rpc :destroy_all, ::Generic::Remote::Tags, ::Generic::Remote::Tags
52
55
  end
56
+
53
57
  end
58
+
54
59
  end
60
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_remote
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hutchison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-26 00:00:00.000000000 Z
11
+ date: 2014-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_attr
@@ -235,12 +235,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
235
235
  version: '0'
236
236
  required_rubygems_version: !ruby/object:Gem::Requirement
237
237
  requirements:
238
- - - '>='
238
+ - - '>'
239
239
  - !ruby/object:Gem::Version
240
- version: '0'
240
+ version: 1.3.1
241
241
  requirements: []
242
242
  rubyforge_project:
243
- rubygems_version: 2.1.11
243
+ rubygems_version: 2.2.1
244
244
  signing_key:
245
245
  specification_version: 4
246
246
  summary: Active Record for your platform