delineate 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c80a9bfec2ba77cc6cdb8ae3f60451cecb42b09e
4
+ data.tar.gz: 7c18f5138e3661086b03e9b3894296bbc09fc86f
5
+ SHA512:
6
+ metadata.gz: 15b35ff7187adb21b2117e9204ec033a80c70f1616541aab7d410e672682747ab3609e6c61b9bdd1230fdbe015f738817984278cc84c3c2d0d5177582c4bc853
7
+ data.tar.gz: d00ed37e44867047b35ace1747be62bb5901c65e8aaf9e5b1532f6e7b3becc15d8f57ace996bcad158c5e01178ac5b8b8e4ce6ccb67d8b26536c9518bda06c96
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Add dependencies required to use your gem here.
4
+ # Example:
5
+ # gem "activesupport", ">= 2.3.5"
6
+
7
+ gem "activerecord", "~>3.2"
8
+ gem "activesupport", "~>3.2"
9
+
10
+ # Add dependencies to develop your gem here.
11
+ # Include everything needed to run rake, tests, features, etc.
12
+ group :development do
13
+ gem "rspec", "~> 2.14"
14
+ gem "rdoc", "~> 3.12"
15
+ gem "bundler", "~> 1"
16
+ gem "jeweler", "~> 2.0"
17
+ gem "simplecov", "~> 0"
18
+ gem "sqlite3", "~> 1"
19
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,90 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.17)
5
+ activesupport (= 3.2.17)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.17)
8
+ activemodel (= 3.2.17)
9
+ activesupport (= 3.2.17)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.17)
13
+ i18n (~> 0.6, >= 0.6.4)
14
+ multi_json (~> 1.0)
15
+ addressable (2.3.5)
16
+ arel (3.0.3)
17
+ builder (3.0.4)
18
+ descendants_tracker (0.0.3)
19
+ diff-lcs (1.2.5)
20
+ docile (1.1.3)
21
+ faraday (0.9.0)
22
+ multipart-post (>= 1.2, < 3)
23
+ git (1.2.6)
24
+ github_api (0.11.2)
25
+ addressable (~> 2.3)
26
+ descendants_tracker (~> 0.0.1)
27
+ faraday (~> 0.8, < 0.10)
28
+ hashie (>= 1.2)
29
+ multi_json (>= 1.7.5, < 2.0)
30
+ nokogiri (~> 1.6.0)
31
+ oauth2
32
+ hashie (2.0.5)
33
+ highline (1.6.20)
34
+ i18n (0.6.9)
35
+ jeweler (2.0.1)
36
+ builder
37
+ bundler (>= 1.0)
38
+ git (>= 1.2.5)
39
+ github_api
40
+ highline (>= 1.6.15)
41
+ nokogiri (>= 1.5.10)
42
+ rake
43
+ rdoc
44
+ json (1.8.1)
45
+ jwt (0.1.11)
46
+ multi_json (>= 1.5)
47
+ mini_portile (0.5.2)
48
+ multi_json (1.8.4)
49
+ multi_xml (0.5.5)
50
+ multipart-post (2.0.0)
51
+ nokogiri (1.6.1)
52
+ mini_portile (~> 0.5.0)
53
+ oauth2 (0.9.3)
54
+ faraday (>= 0.8, < 0.10)
55
+ jwt (~> 0.1.8)
56
+ multi_json (~> 1.3)
57
+ multi_xml (~> 0.5)
58
+ rack (~> 1.2)
59
+ rack (1.5.2)
60
+ rake (10.1.1)
61
+ rdoc (3.12.2)
62
+ json (~> 1.4)
63
+ rspec (2.14.1)
64
+ rspec-core (~> 2.14.0)
65
+ rspec-expectations (~> 2.14.0)
66
+ rspec-mocks (~> 2.14.0)
67
+ rspec-core (2.14.7)
68
+ rspec-expectations (2.14.5)
69
+ diff-lcs (>= 1.1.3, < 2.0)
70
+ rspec-mocks (2.14.6)
71
+ simplecov (0.8.2)
72
+ docile (~> 1.1.0)
73
+ multi_json
74
+ simplecov-html (~> 0.8.0)
75
+ simplecov-html (0.8.0)
76
+ sqlite3 (1.3.8)
77
+ tzinfo (0.3.38)
78
+
79
+ PLATFORMS
80
+ ruby
81
+
82
+ DEPENDENCIES
83
+ activerecord (~> 3.2)
84
+ activesupport (~> 3.2)
85
+ bundler (~> 1)
86
+ jeweler (~> 2.0)
87
+ rdoc (~> 3.12)
88
+ rspec (~> 2.14)
89
+ simplecov (~> 0)
90
+ sqlite3 (~> 1)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Tom Smith
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,292 @@
1
+ *NOTE*: This code is published for the purpose of exposing some of my work, and is
2
+ not yet intended to be a fully productized library. Please consider this an
3
+ implementation sample only. The library will be packaged as a full gem real soon now.
4
+
5
+
6
+ = Delineate
7
+
8
+ The delineate gem provides ActiveRecord serialization DSL for mapping model attributes and associations.
9
+ The functionality is similar in concept to that provided by ActiveModel Serializers with many enhancements,
10
+ including built in bi-directional support, i.e. deserialization (parsing) of attributes and nested
11
+ associations.
12
+
13
+ == About Attribute Maps
14
+
15
+ ActiveRecord attribute maps provide the ability to expose access to an ActiveRecord
16
+ model's attributes and associations in a customized way. When you specify an
17
+ attribute map, you decouple the model's internal attributes and associations from
18
+ its "presentation" or interface, allowing programmatic interaction with the model
19
+ to remain consistent even when the model implementation or schema changes.
20
+
21
+ Attribute maps essentially let you create an interface to the ActiveRecord model
22
+ that is different from its internal application interface that a controller might
23
+ typically use. For example, you may wish to involve the model in the API to your
24
+ application. When declaring an attribute map, you decide which attributes and
25
+ nested association model attributes to expose, what their public names are,
26
+ and the access level for each. By invoking simple method calls you can read or
27
+ write these attribute values through the map thereby using it to "render" the
28
+ declared attributes and associations.
29
+
30
+ Attribute maps are named, which means for a given model you can declare maps
31
+ for any number of use cases. In a single model, for example, you could define
32
+ one map to facilitate implementing a public API, another for a private
33
+ inter-application API, and yet another for data exchange (import and export).
34
+
35
+ Key features:
36
+
37
+ * Multiple attribute maps per ActiveRecord model.
38
+ * Map serializers are used for both reading and writing attributes and
39
+ nested models. Hash, JSON, XML and CSV serializers are built in.
40
+ * A mapped attribute or association can be assigned a name that is different
41
+ from the name used internally in the model.
42
+ * Access to a mapped attribute or association can be designated read-only
43
+ or read-write.
44
+ * A mapped association can automatically use the map defined in its
45
+ model class (merging in modifications), or can completely override
46
+ the map declared in its model.
47
+ * Special handling for STI: subclasses inherit and/or override maps from
48
+ their base class, and the appropriate STI class is used when reading
49
+ attributes.
50
+ * A mapped attribute or association can be declared as optional (or placed
51
+ in an option group). Optional attributes are serialized only when
52
+ explicitly included.
53
+
54
+ == Declaring Attribute Maps
55
+
56
+ To define an attribute map in an ActiveRecord model you invoke the
57
+ +map_attributes+ class method. For example:
58
+
59
+ class Post < ActiveRecord::Base
60
+ belongs_to :author
61
+ has_many :post_topics
62
+ has_many :topics, :through => :post_topics
63
+ has_many :comments
64
+
65
+ map_attributes :api do
66
+ attribute :title
67
+ attribute :content, :using => :body
68
+ attribute :created_at, :access => :ro
69
+
70
+ association :author
71
+ association :comments, :optional => true
72
+ end
73
+ end
74
+
75
+ The +map_attributes+ method creates an attribute map with the specified
76
+ name and associates it with the model class. The map DSL specifies which
77
+ attributes and associations are included, their external names, access
78
+ permissions, and other options. In this example, the map is named :api
79
+ and might be used by an API controller to read and write data related
80
+ to the Post resource. Three of the Post attributes are exposed through
81
+ the API as well as the attributes of the +author+ association, according
82
+ to the :api attribute map defined in the Author class. The attributes
83
+ of the +comments+ association are processed only when specifically
84
+ included in subsequent serialization calls.
85
+
86
+ As a result of this declaration, two Post instance methods are defined
87
+ for accessing the model attributes through the map. So, for example, a
88
+ controller could do:
89
+
90
+ p = Post.first
91
+ attrs = p.api_attributes
92
+
93
+ to retrieve an attributes hash as processed through the Post attribute map
94
+ named +:api+.
95
+
96
+ Or do something like:
97
+
98
+ p = Post.first
99
+ attrs = Hash.from_xml(xml_string)
100
+ p.api_attributes = attrs
101
+ p.save!
102
+
103
+ which will update only those attributes and nested models (and according to
104
+ their "public" names) as specified in the :api attribute map.
105
+
106
+ See the Delineate::AttributeMap class for details about the DSL.
107
+
108
+ === Mapping Model Attributes
109
+
110
+ To declare a model attribute be included in the map, you use the +attribute+
111
+ method on the AttributeMap instance:
112
+
113
+ attribute :public_name, :access => :rw, :using => :internal_name
114
+
115
+ The first parameter is required and is the map-specific public name for the
116
+ attribute. If the :using parameter is not provided, the external name
117
+ and internal name are assumed to be identical. If :using is specified,
118
+ the name provided must be either an existing model attribute, or a method
119
+ that will be called when reading/writing the attribute. In the example above,
120
+ if +internal_name+ is not a model attribute, you must define methods
121
+ +internal_name+ and +internal_name=(value)+, the latter being required if the
122
+ attribute is not read-only.
123
+
124
+ The :access parameter can take the following values:
125
+
126
+ :rw This value, which is the default, means that the attribute is read-write.
127
+ :ro The :ro value designates the attribute as read-only. Attempts to set the
128
+ attribute's value will silently fail.
129
+ :w The attribute value can be set when a model instance is created,
130
+ but read-only after that.
131
+
132
+ The :optional parameter affects the reading of a model attribute:
133
+
134
+ attribute :balance, :access => :ro, :optional => true
135
+
136
+ Optional attributes are not accessed/included when retrieving the mapped
137
+ attributes, unless explicitly requested. This can be useful when there are
138
+ performance implications for calculating an attribute's value for example. You
139
+ can specify a symbol as the value for :optional instead of true. The symbol
140
+ then groups together all attributes with that option group. For example, if
141
+ you specify:
142
+
143
+ attribute :balance, :access => :ro, :optional => :compute_balances
144
+ attribute :total_balance, :access => :ro, :optional => :compute_balances
145
+
146
+ you then get:
147
+
148
+ acct.api_attributes(:include => :balance) # :balance attribute is included in result
149
+ acct.api_attributes(:include => :compute_balances) # Both :balance and :total_balance attributes are returned
150
+
151
+ The :read and :write parameters are used to define simple accessor methods
152
+ for the attribute. The specified lambda will be defined as a method named
153
+ by the :using parameter. For example:
154
+
155
+ attribute :parent, :using => :parent_api,
156
+ :read => lambda {|a| a.parent ? a.parent.path : nil},
157
+ :write => lambda {|a, v| a.parent = {:path => v}}
158
+
159
+ Two methods, +parent_api()+ and +parent_api=(value)+ will be defined on the
160
+ model. In this example, if the :write parameter is ommitted, you must provide
161
+ a write accessor method for the parent_api attribute in the model code.
162
+
163
+ === Mapping Model Associations
164
+
165
+ In addition to attributes, you can specify a model's associations in an
166
+ attribute map. For example:
167
+
168
+ class Account < ActiveRecord::Base
169
+ :belongs_to :account_type
170
+ map_attributes :api do
171
+ attribute :name
172
+ attribute :path, :access => :ro
173
+ association :type, :using => :account_type
174
+ end
175
+ end
176
+
177
+ The first parameter in the association specification is its mapped name, and the
178
+ optional :using parameter is the internal association name. In the example
179
+ above the +:account_type+ association is exposed as a nested object
180
+ named 'type'.
181
+
182
+ When specifying an association mapping, by default the attribute map in
183
+ the association's model class is used to define its attributes and nested
184
+ associations. If you include an attribute defininiton in the association map,
185
+ it will override the spec in the association model:
186
+
187
+ class Account < ActiveRecord::Base
188
+ :belongs_to :account_type
189
+ map_attributes :api do
190
+ attribute :path, :access => :ro
191
+ association :type, :using => :account_type do
192
+ attribute :name, :access => :ro
193
+ attribute :description, :access => :ro
194
+ end
195
+ end
196
+ end
197
+
198
+ In this example, if the AccountType attribute map declared :name as
199
+ read-write, the association map in the Account model overrides that to make :name
200
+ read-only when accessed as a nested object from an Account model. If the
201
+ :description attribute of AccountType had not been specified in the AccountType
202
+ attribute map, the inclusion of it here lets that attribute be exposed in the
203
+ Account attribute map. Note that when overriding an association's attribute, the
204
+ override must completely re-define the attribute's options.
205
+
206
+ If you want to fully specify an association's attributes, use the
207
+ :override option as follows:
208
+
209
+ class Account < ActiveRecord::Base
210
+ :belongs_to :account_type
211
+ map_attributes :api do
212
+ association :type, :using => :account_type, :override => :replace do
213
+ attribute :name, :access => :ro
214
+ attribute :description, :access => :ro
215
+ association :category, :access => :ro :using => :account_category
216
+ attribute :name
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ which re-defines the mapped association as viewed by Account; no merging is
223
+ done with the attribute map defined in the AccountType model. In the example
224
+ above, note the ability to nest associations. For this to work, account_category
225
+ must be declared as an ActiveRecord association in the AccountType class.
226
+
227
+ Other parameters for mapping associations:
228
+
229
+ :access As with attributes, an association can be declared :ro or :rw (the
230
+ default). An association that is writeable must have a
231
+ an +accepts_nested_attributes_for+ declaration defined in the
232
+ parent model. This allows attribute writes to contain a nested
233
+ hash for the association (except for individual association
234
+ attributes that are read-only).
235
+
236
+ :optional When set to true, the association is not included by default when
237
+ retrieving/returning the model's mapped attributes.
238
+
239
+ :polymorphic Affects reading only and is relevant when the association class
240
+ is an STI base class. When set to true, the attribute map of
241
+ each association record (as defined by its class) is used to
242
+ specify its included attributes and associations. This means that
243
+ in a collection association, the returned attribute hashes may be
244
+ heterogeneous, i.e. vary according to each retrieved record's
245
+ class. NOTE: when using :polymorphic, you cannot merge/override
246
+ the association class attribute map.
247
+
248
+ === STI Attribute Maps
249
+
250
+ ActiveRecord STI subclasses inherit the attribute maps from their superclass.
251
+ If you want to include additional subclass attributes, just invoke
252
+ map_attributes in the subclass and define the extra attributes and
253
+ associations. If the subclass wants to completely override/replace the
254
+ superclass map, do:
255
+
256
+ class MySubclass < MyBase
257
+ map_attributes :api, :override => :replace do
258
+ .
259
+ .
260
+ end
261
+ end
262
+
263
+ == Serialization and Using Attribute Maps
264
+
265
+ === Serializng Out
266
+
267
+ === Serializing In
268
+
269
+
270
+ == Roadmap
271
+
272
+ Here are the things I'll be working on next:
273
+
274
+ * More tests around the JSON, XML, and CSV serializers.
275
+ * Design and implementation for importing from CSV through attribute maps.
276
+ * API controller framework taking advantage of attribute maps.
277
+ * Refactor CTI support
278
+
279
+ == Contributing to delineate
280
+
281
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
282
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
283
+ * Fork the project
284
+ * Start a feature/bugfix branch
285
+ * Commit and push until you are happy with your contribution
286
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
287
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
288
+
289
+ == Copyright
290
+
291
+ Copyright (c) 2011-2014 Tom Smith. See LICENSE.txt for further details.
292
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "delineate"
18
+ gem.homepage = "http://github.com/rtomsmith/delineate"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{ActiveRecord serializer DSL for mapping model attributes and associations}
21
+ gem.description = %Q{ActiveRecord serializer DSL for mapping model attributes and associations. Similar to ActiveModel Serializers with many enhancements including bi-directional support, i.e. deserialization.}
22
+ gem.email = "tsmith@landfall.com"
23
+ gem.authors = ["Tom Smith"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ desc "Code coverage detail"
35
+ task :simplecov do
36
+ ENV['COVERAGE'] = "true"
37
+ Rake::Task['spec'].execute
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rdoc/task'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "delineate #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end