encore 0.0.2 → 0.0.3

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: b6425ceec678f308c80da211bf16946506b2c704
4
- data.tar.gz: 481ae8d7b56757c499fa9cebae51f6eb635052e5
3
+ metadata.gz: c05497c6b6640c47a20eafba79c3eb344cc523a6
4
+ data.tar.gz: 1e727f1662fdf0927f940e0a4d46fa5054073d88
5
5
  SHA512:
6
- metadata.gz: e7c056d64a6c7d8341fa8728255b1c37da9156e9f3800245911069c345a6a2250fbf91aa3a531417c4c7f7e668e08b4579448e393582f980d9e04434ae148bc0
7
- data.tar.gz: 717a8560dee3c08e3bf6cbf3e05b5239dcd5273e16c2df82b99eb93ab36d2357f1a80a61bc155c14b7473f6302d0ddb99d2bfe87fc75dcfa682cdade62ce9fe6
6
+ metadata.gz: 2b0c3d9175e9665cb709d725cf48873f2c16a4b9fb4100af1dafc07f087a9a9a075fbb8492822624a186386f4b5a0e7f25c560fdfd3b02dee4efd43e89820157
7
+ data.tar.gz: 46d9c310cdaed3ea98051dea98f3a463137468f512abadfd62019553e74dd4c35e0c88d9531f14d47c289b358b516d82ed2e208b6f1059ba87fb3d1a5796c7f2
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.0.0
5
+ - 1.9.3
6
+
7
+ gemfile:
8
+ - gemfiles/Gemfile.activerecord-4.0
9
+ - gemfiles/Gemfile.activerecord-3.2.x
10
+
11
+ script: "echo 'DO IT' && bundle exec rake spec"
data/README.md CHANGED
@@ -1,6 +1,16 @@
1
- # Encore
2
-
3
- Encore is a framework built on top of ActiveRecord to manage entities.
1
+ <p align="center">
2
+ <a href="https://github.com/mirego/encore">
3
+ <img src="http://i.imgur.com/erXBozp.png" alt="Encore" />
4
+ </a>
5
+ <br />
6
+ Encore is a Ruby framework to help dealing with entities.
7
+ <br /><br />
8
+ <a href="https://rubygems.org/gems/encore"><img src="https://badge.fury.io/rb/encore.png" /></a>
9
+ <a href="https://codeclimate.com/github/mirego/encore"><img src="https://codeclimate.com/github/mirego/encore.png" /></a>
10
+ <a href="https://travis-ci.org/mirego/encore"><img src="https://travis-ci.org/mirego/encore.png?branch=master" /></a>
11
+ </p>
12
+
13
+ ---
4
14
 
5
15
  ## Installation
6
16
 
@@ -24,6 +34,109 @@ $ gem install encore
24
34
 
25
35
  ## Usage
26
36
 
37
+ Let’s say we have the `UserEntity` entity class bound to the `User` model:
38
+
39
+ ```ruby
40
+ class User < ActiveRecord::Base
41
+ end
42
+
43
+ class UserEntity
44
+ include Encore::Entity
45
+
46
+ expose :name
47
+ expose :email
48
+ expose :created_at, readonly: true
49
+ expose :updated_at, readonly: true
50
+ end
51
+ ```
52
+
53
+ We can create a new `User` resource with attributes:
54
+
55
+ ```ruby
56
+ @entity = UserEntity.new
57
+ @entity.assign_attributes email: 'remi@example.com', name: 'Rémi Prévost'
58
+ @entity.save
59
+ ```
60
+
61
+ When assigning attributes to an entity, we can pass either an `:update` or a `:partial_update` context.
62
+
63
+ With the (default) `:partial_update` context, Encore will assign new attributes and ignore the other exposed attributes. This makes sense in a `PATCH` HTTP request context.
64
+
65
+ ```ruby
66
+ new_attributes = { email: 'remi+new@example.com' }
67
+ @entity.assign_attributes new_attributes
68
+ @entity.object.email # => "remi+new@example.com"
69
+ @entity.object.name # => "Rémi Prévost"
70
+ ```
71
+
72
+ However, with the `:update` context, Encore will assign new attributes and set all non-provided exposed attributes to `nil`. This
73
+ makes sense in a `PUT` HTTP request context.
74
+
75
+ ```ruby
76
+ new_attributes = { email: 'remi+new-again@example.com' }
77
+ @entity.assign_attributes new_attributes, context: :update
78
+ @entity.object.email # => "remi+new-again@example.com"
79
+ @entity.object.name # => nil
80
+ ```
81
+
82
+ If we try to assign a value to a non-exposed or readonly attribute, Encore will raise an error.
83
+
84
+ ```ruby
85
+ @entity.assign_attributes email: 'remi+new@example.com', created_at: Time.now
86
+ # => raises #<Encore::Entity::Input::InvalidAttributeError: The #<Encore::Attribute UserEntity@created_at> attribute is not exposed.>
87
+ ```
88
+
89
+ ### Associations
90
+
91
+ You can also expose associations.
92
+
93
+ ```ruby
94
+ class User < ActiveRecord::Base
95
+ belongs_to :organization
96
+ end
97
+
98
+ class Organization < ActiveRecord::Base
99
+ has_many :users
100
+ end
101
+
102
+ class UserEntity
103
+ include Encore::Entity
104
+
105
+ expose :name
106
+ expose_one :organization
107
+ end
108
+ ```
109
+
110
+ Assigning new value for associations doesn’t save them right away.
111
+
112
+ ```ruby
113
+ @user = User.first # => #<User id=1 organization_id=1>
114
+ @entity = UserEntity.new(@user)
115
+ @entity.assign_attributes organization: 2
116
+ @entity.object.organization_id # => 1
117
+ ```
118
+
119
+ Calling `save` on the entity saves them.
120
+
121
+ ```ruby
122
+ @entity.save
123
+ @entity.object.organization_id # => 2
124
+ ```
125
+
126
+ ## Typical setup with Ruby on Rails
127
+
128
+ _This is work-in-progress. There’s still missing stuff._
129
+
130
+ ### Model
131
+
132
+ ```ruby
133
+ # app/models/user.rb
134
+ class User < ActiveRecord::Base
135
+ end
136
+ ```
137
+
138
+ ### Entity
139
+
27
140
  ```ruby
28
141
  # app/entities/user_entity.rb
29
142
  class UserEntity
@@ -34,43 +147,81 @@ class UserEntity
34
147
  expose :created_at, readonly: true
35
148
  expose :updated_at, readonly: true
36
149
  end
150
+ ```
151
+
152
+ ### Routes
153
+
154
+ ```ruby
155
+ # config/routes.rb
156
+ Rails::Application.routes.draw do
157
+ resources :users do
158
+ # This makes Rails route PUT and PATCH requests to two separate actions
159
+ patch on: :member, action: :partial_update
160
+ end
161
+ end
162
+ ```
163
+
164
+ ### Controller
37
165
 
166
+ ```ruby
38
167
  # app/controllers/users_controller.rb
39
168
  class UsersController < ApplicationController
169
+ before_action :fetch_user, only: [:update, :partial_update]
170
+
40
171
  # POST /users
41
172
  def create
42
- @user = UserEntity.new
43
- @user.assign_attributes(params[:user], context: :create)
44
-
45
- if @user.save
46
- render json: @user, status: 201
47
- else
48
- render json: { errors: @user.errors }, status: 422
49
- end
173
+ @entity = UserEntity.new
174
+ @entity.assign_attributes(params[:user], context: :create)
175
+
176
+ process! @entity
177
+
178
+ # Here, `process!` is a shortcut for:
179
+ #
180
+ # if @entity.save
181
+ # render json: @entity, status: 201
182
+ # else
183
+ # render json: { errors: @entity.errors }, status: 422
184
+ # end
50
185
  end
51
186
 
52
187
  # PUT /users/:id
53
188
  def update
54
- @user = UserEntity.new User.find(params[:id])
55
- @user.assign_attributes(params[:user], context: :update)
56
-
57
- if @user.save
58
- render json: @user
59
- else
60
- render json: { errors: @user.errors }, status: 422
61
- end
189
+ @entity = UserEntity.new(@user)
190
+ @entity.assign_attributes(params[:user], context: :update)
191
+
192
+ process! @entity
62
193
  end
63
194
 
64
195
  # PATCH /users/:id
65
196
  def partial_update
66
- @user = UserEntity.find User.find(params[:id])
67
- @user.assign_attributes(params[:user], context: :partial_update)
68
-
69
- if @user.save
70
- render json: @user
71
- else
72
- render json: { errors: @user.errors }, status: 422
73
- end
197
+ @entity = UserEntity.new(@user)
198
+ @entity.assign_attributes(params[:user], context: :partial_update)
199
+
200
+ process! @entity
201
+ end
202
+
203
+ protected
204
+
205
+ def fetch_user
206
+ @user = User.find(params[:id])
74
207
  end
75
208
  end
76
209
  ```
210
+
211
+ ## Todo
212
+
213
+ Please keep in mind that this gem is far from finished and totally not ready to use in production.
214
+ This is something we’ve been wanting to build for a long time and now we’re finally taking the time
215
+ do it right.
216
+
217
+ ## License
218
+
219
+ `Encore` is © 2013 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/encore/blob/master/LICENSE.md) file.
220
+
221
+ The nut logo is based on [this lovely icon](http://thenounproject.com/noun/hazelnuts/#icon-No3618) by [Alessandro Suraci](http://thenounproject.com/alessandro.suraci/), from The Noun Project. Used under a [Creative Commons BY 3.0](http://creativecommons.org/licenses/by/3.0/) license.
222
+
223
+ ## About Mirego
224
+
225
+ Mirego is a team of passionate people who believe that work is a place where you can innovate and have fun. We proudly build mobile applications for [iPhone](http://mirego.com/en/iphone-app-development/ "iPhone application development"), [iPad](http://mirego.com/en/ipad-app-development/ "iPad application development"), [Android](http://mirego.com/en/android-app-development/ "Android application development"), [Blackberry](http://mirego.com/en/blackberry-app-development/ "Blackberry application development"), [Windows Phone](http://mirego.com/en/windows-phone-app-development/ "Windows Phone application development") and [Windows 8](http://mirego.com/en/windows-8-app-development/ "Windows 8 application development") in beautiful Quebec City.
226
+
227
+ We also love [open-source software](http://open.mirego.com/) and we try to extract as much code as possible from our projects to give back to the community.
data/encore.gemspec CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Encore::VERSION
9
9
  spec.authors = ['Rémi Prévost']
10
10
  spec.email = ['rprevost@mirego.com']
11
- spec.description = 'Encore is a framework built on top of ActiveRecord to manage entities.'
11
+ spec.description = 'Encore is a Ruby framework to help dealing with entities.'
12
12
  spec.summary = spec.description
13
- spec.homepage = 'https://github.com/remiprev/encore'
13
+ spec.homepage = 'https://github.com/mirego/encore'
14
14
  spec.license = "BSD 3-Clause"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rake'
23
23
  spec.add_development_dependency 'rspec'
24
24
  spec.add_development_dependency 'sqlite3'
25
+ spec.add_development_dependency 'rails', '>= 3.0.0'
25
26
 
26
- spec.add_dependency 'activerecord', '>= 3.0.0'
27
27
  spec.add_dependency 'activesupport', '>= 3.0.0'
28
28
  end
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'activerecord', '~> 3.2.0'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'activerecord', '~> 4.0.0'
@@ -0,0 +1,12 @@
1
+ module Encore
2
+ class Association
3
+ class HasManyAssociation < Association
4
+
5
+ protected
6
+
7
+ def column_accessor
8
+ :"#{self.column.to_s.singularize}_ids"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Encore
2
+ class Association
3
+ class HasOneAssociation < Association
4
+
5
+ protected
6
+
7
+ def column_accessor
8
+ :"#{self.column}_id"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ module Encore
2
+ class Association < Attribute
3
+ def initialize(*args)
4
+ super
5
+
6
+ # Create a temporary accessor to store new values
7
+ @klass.send :attr_accessor, self.tmp_column_accessor
8
+
9
+ # Add callback on `before_save` to apply new values
10
+ association = self
11
+ @klass.before_save lambda { association.apply(self) }
12
+ end
13
+
14
+ # Store the value in a temporary accessor used by the `before_save` callback
15
+ def assign(object, value)
16
+ object.send :"#{tmp_column_accessor}=", value
17
+ end
18
+
19
+ # Apply the new value to the column and reset the temporary accessor
20
+ def apply(object)
21
+ object.send :"#{column_accessor}=", object.send(tmp_column_accessor)
22
+ object.send :"#{tmp_column_accessor}=", nil
23
+ end
24
+
25
+ # Return the value of the association
26
+ def fetch(object)
27
+ object.send :"#{column_accessor}"
28
+ end
29
+
30
+ protected
31
+
32
+ def tmp_column_accessor
33
+ :"encore_tmp_#{column_accessor}"
34
+ end
35
+ end
36
+ end
37
+
38
+ require 'encore/association/has_one_association'
39
+ require 'encore/association/has_many_association'
@@ -1,17 +1,20 @@
1
1
  module Encore
2
2
  class Attribute
3
- attr_reader :attribute, :klass
3
+ attr_reader :attribute, :klass, :column, :opts
4
4
 
5
- def initialize(klass, attribute, opts = {})
5
+ def initialize(klass, column, opts = {})
6
6
  @klass = klass
7
- @attribute = attribute
7
+ @column = column
8
+ @attribute = opts[:as].try(:to_sym) || @column
8
9
  @opts = opts
9
10
  end
10
11
 
11
- def self.map_attributes(klass, params)
12
- params.inject({}) do |memo, (attribute, value)|
13
- memo.merge Attribute.new(klass, attribute.to_sym) => value
14
- end
12
+ def assign(object, value)
13
+ object.send :"#{self.column}=", value
14
+ end
15
+
16
+ def fetch(object)
17
+ object.send :"#{self.column}"
15
18
  end
16
19
 
17
20
  def ==(other_attribute)
@@ -0,0 +1,27 @@
1
+ module Encore
2
+ module Entity
3
+ module Input
4
+ module Associations
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def expose_one(column, opts = {})
9
+ expose_association(:one, column, opts)
10
+ end
11
+
12
+ def expose_many(column, opts = {})
13
+ expose_association(:many, column, opts)
14
+ end
15
+
16
+ protected
17
+
18
+ def expose_association(type, column, opts)
19
+ @exposed_attributes ||= []
20
+ association_class = Encore::Association.const_get("Has#{type.to_s.capitalize}Association")
21
+ @exposed_attributes << association_class.new(self, column.to_sym, opts)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,7 +1,7 @@
1
1
  module Encore
2
2
  module Entity
3
3
  module Input
4
- class ProtectedAttributeError < StandardError
4
+ class InvalidAttributeError < StandardError
5
5
  end
6
6
  end
7
7
  end
@@ -6,14 +6,9 @@ module Encore
6
6
 
7
7
  module ClassMethods
8
8
  # Declare a new exposed attribute
9
- def expose(attribute, opts = {})
9
+ def expose(column, opts = {})
10
10
  @exposed_attributes ||= []
11
- @exposed_attributes << Encore::Attribute.new(self, attribute.to_sym, opts)
12
- end
13
-
14
- # Return only the attribute keys for each exposed attribute
15
- def exposed_attributes_list
16
- @exposed_attributes.map(&:attribute)
11
+ @exposed_attributes << Encore::Attribute.new(self, column.to_sym, opts)
17
12
  end
18
13
 
19
14
  # Return whether an attribute is exposed by the entity
@@ -26,6 +21,11 @@ module Encore
26
21
  modifiable_attributes.include?(attribute)
27
22
  end
28
23
 
24
+ # Return a list of all exposed attributes
25
+ def exposed_attributes
26
+ @exposed_attributes
27
+ end
28
+
29
29
  # Return a list of all attributes that can be modified
30
30
  def modifiable_attributes
31
31
  @exposed_attributes.reject(&:readonly?)
@@ -1,5 +1,6 @@
1
1
  require 'encore/entity/input/errors'
2
2
  require 'encore/entity/input/exposed_attributes'
3
+ require 'encore/entity/input/associations'
3
4
 
4
5
  module Encore
5
6
  module Entity
@@ -7,48 +8,66 @@ module Encore
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  included do
11
+ extend ActiveModel::Callbacks
10
12
  include Encore::Entity::Input::ExposedAttributes
13
+ include Encore::Entity::Input::Associations
14
+ define_model_callbacks :save
11
15
 
12
16
  # Expose ID as a readonly attribute
13
17
  expose :id, readonly: true
18
+
19
+ # Make the context accessible
20
+ attr_reader :context
14
21
  end
15
22
 
16
23
  # Assign specific attribute to the object
17
24
  def assign_attributes(attributes = {}, opts = nil)
18
25
  # Find the right context
19
- unless context = opts.try(:delete, :context)
20
- context = @object.persisted? ? :create : :partial_update
26
+ unless @context = opts.try(:delete, :context)
27
+ @context = @object.persisted? ? :partial_update : :create
21
28
  end
22
29
 
23
30
  # Convert attribute keys to Attribute objects
24
- attributes = Encore::Attribute.map_attributes(self.class, attributes)
31
+ attributes = self.class.map_attributes(self.class, attributes)
25
32
 
26
33
  # Loop through every passed attribute and set it
27
- assign_provided_attributes(attributes, context)
34
+ assign_provided_attributes(attributes)
28
35
 
29
36
  # If we're doing an update, for any exposed attribute that wasn't passed, set it to nil
30
- reset_forgotten_attributes(attributes, context)
37
+ reset_forgotten_attributes(attributes)
31
38
  end
32
39
 
33
40
  protected
34
41
 
35
- def assign_provided_attributes(attributes, context)
42
+ def assign_provided_attributes(attributes)
36
43
  attributes.each_pair do |attribute, value|
37
44
  if self.class.modifiable_attribute?(attribute)
38
- send :"#{attribute.attribute}=", value
45
+ attribute.assign(self, value)
39
46
  else
40
- raise Encore::Entity::Input::ProtectedAttributeError.new("The #{attribute} attribute is not exposed.")
47
+ raise Encore::Entity::Input::InvalidAttributeError.new("The #{attribute} attribute is not exposed.")
41
48
  end
42
49
  end
43
50
  end
44
51
 
45
- def reset_forgotten_attributes(attributes, context)
46
- if context == :update
52
+ def reset_forgotten_attributes(attributes)
53
+ if @context == :update
47
54
  (self.class.modifiable_attributes - attributes.keys).each do |attribute|
48
- send :"#{attribute.attribute}=", nil
55
+ attribute.assign(self, nil)
49
56
  end
50
57
  end
51
58
  end
59
+
60
+ module ClassMethods
61
+ def map_attributes(klass, params)
62
+ params.inject({}) do |memo, (attribute, value)|
63
+ exposed_attribute = klass.exposed_attributes.detect { |a| a.attribute == attribute }
64
+ key = exposed_attribute || Attribute.new(klass, attribute.to_sym)
65
+
66
+ memo.merge key => value
67
+ end
68
+ end
69
+
70
+ end
52
71
  end
53
72
  end
54
73
  end
@@ -0,0 +1,14 @@
1
+ module Encore
2
+ module Entity
3
+ module Output
4
+ module JSON
5
+ # Return each exposed attribute as a JSON value
6
+ def as_json(args = {})
7
+ self.class.exposed_attributes.inject({}) do |memo, item|
8
+ memo.merge item.attribute => item.fetch(self)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,7 +1,13 @@
1
+ require 'encore/entity/output/json'
2
+
1
3
  module Encore
2
4
  module Entity
3
5
  module Output
4
6
  extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Encore::Entity::Output::JSON
10
+ end
5
11
  end
6
12
  end
7
13
  end
data/lib/encore/entity.rb CHANGED
@@ -19,9 +19,18 @@ module Encore
19
19
  @object = object || self.class.linked_class.new
20
20
  end
21
21
 
22
+ # Save the object
23
+ def save
24
+ run_callbacks(:save) { @object.save }
25
+ end
26
+
22
27
  # Delegate everything to the object
23
28
  def method_missing(method, *args, &blk)
24
- @object.send(method, *args, &blk)
29
+ if @object.respond_to?(method)
30
+ @object.send(method, *args, &blk)
31
+ else
32
+ super
33
+ end
25
34
  end
26
35
 
27
36
  module ClassMethods
@@ -0,0 +1,37 @@
1
+ module Encore
2
+ module Helpers
3
+ module ControllerHelper
4
+ HTTP_ERROR_STATUS_CODE = 422
5
+ HTTP_SUCCESS_CODE = 200
6
+ HTTP_SUCCESS_CREATED_CODE = 201
7
+
8
+ # Inject itself into the ActionController::Base class
9
+ def self.inject_into_action_controller
10
+ ::ActionController::Base.send :include, self
11
+ end
12
+
13
+ # Save the entity and return a JSON response
14
+ def process!(entity)
15
+ error_status = HTTP_ERROR_STATUS_CODE
16
+ success_status = success_status_code_from_entity(entity)
17
+
18
+ if entity.save
19
+ render json: entity, status: success_status
20
+ else
21
+ render json: { errors: entity.errors }, status: error_status
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ # Return the status code to send after a successful processing
28
+ # of an entity, depending on its context
29
+ def success_status_code_from_entity(entity)
30
+ case entity.context
31
+ when :create then HTTP_SUCCESS_CREATED_CODE
32
+ else HTTP_SUCCESS_CODE
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ require 'encore/helpers/controller_helper'
@@ -0,0 +1,11 @@
1
+ require 'encore'
2
+ require 'rails'
3
+ require 'action_controller'
4
+
5
+ module Encore
6
+ class Railtie < Rails::Railtie
7
+ initializer 'encore.action_controller' do |app|
8
+ ActiveSupport.on_load :action_controller, {}, &Encore::Helpers::ControllerHelper.inject_into_action_controller
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Encore
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/lib/encore.rb CHANGED
@@ -3,9 +3,11 @@ require 'encore/version'
3
3
  # ActiveSupport
4
4
  require 'active_support'
5
5
 
6
- # ActiveRecord
7
- require 'active_record'
8
-
9
6
  # Encore
10
7
  require 'encore/entity'
11
8
  require 'encore/attribute'
9
+ require 'encore/association'
10
+ require 'encore/helpers'
11
+
12
+ # Railtie
13
+ require 'encore/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Entity::Input::Associations do
4
+ describe :ClassMethods do
5
+ describe :has_one do
6
+ before do
7
+ spawn_model(:User) { belongs_to :organization }
8
+ spawn_model(:Organization) { has_many :users }
9
+
10
+ spawn_entity :UserEntity do
11
+ expose :name
12
+ expose_one :organization
13
+ end
14
+
15
+ run_migration do
16
+ create_table(:users, force: true) do |t|
17
+ t.string :name
18
+ t.integer :organization_id
19
+ t.timestamps
20
+ end
21
+
22
+ create_table(:organizations, force: true)
23
+ end
24
+ end
25
+
26
+ let(:organization) { Organization.create }
27
+ let(:other_organization) { Organization.create }
28
+ let(:user) { organization.users.create(name: 'Art Vandelay') }
29
+ let(:entity) { UserEntity.new(user) }
30
+
31
+ context 'when assigning attributes' do
32
+ before { entity.assign_attributes organization: other_organization.id }
33
+ it { expect(entity.object.organization_id).to eql organization.id }
34
+ it { expect(entity.encore_tmp_organization_id).to eql other_organization.id }
35
+ end
36
+
37
+ context 'when saving the entity' do
38
+ before do
39
+ entity.assign_attributes organization: other_organization.id
40
+ entity.save
41
+ end
42
+
43
+ it { expect(entity.object.organization_id).to eql other_organization.id }
44
+ it { expect(entity.encore_tmp_organization_id).to be_nil }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Entity::Input::ExposedAttributes do
4
+ shared_examples 'exposability' do
5
+ before do
6
+ spawn_object_class!
7
+ end
8
+
9
+ context 'with attribute name' do
10
+ it { expect{ entity.assign_attributes(name: 'Art Vandelay') }.to raise_error(Encore::Entity::Input::InvalidAttributeError) }
11
+ end
12
+
13
+ context 'with column name' do
14
+ before { entity.assign_attributes fullname: 'Art Vandelay' }
15
+ it { expect(entity.object.name).to eql 'Art Vandelay' }
16
+ end
17
+ end
18
+
19
+ let(:entity) { UserEntity.new }
20
+
21
+ before do
22
+ spawn_entity :UserEntity do
23
+ expose :name, as: :fullname
24
+ end
25
+ end
26
+
27
+ context 'with ActiveRecord model' do
28
+ let(:spawn_object_class!) do
29
+ spawn_model :User
30
+
31
+ run_migration do
32
+ create_table(:users, force: true) do |t|
33
+ t.string :name
34
+ t.timestamps
35
+ end
36
+ end
37
+ end
38
+
39
+ it_has_behavior 'exposability'
40
+ end
41
+
42
+ context 'with OpenStruct structure' do
43
+ let(:spawn_object_class!) { spawn_struct :User }
44
+ it_has_behavior 'exposability'
45
+ end
46
+ end
@@ -1,79 +1,79 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Encore::Entity do
4
- before do
5
- spawn_model :User
6
-
7
- spawn_entity :UserEntity do
8
- expose :name
9
- expose :email
10
- expose :created_at, readonly: true
11
- expose :updated_at, readonly: true
12
- end
3
+ describe Encore::Entity::Input do
4
+ describe :InstanceMethods do
5
+ describe :assign_attributes do
6
+ before do
7
+ spawn_model :User
8
+
9
+ spawn_entity :UserEntity do
10
+ expose :name
11
+ expose :email
12
+ expose :created_at, readonly: true
13
+ expose :updated_at, readonly: true
14
+ end
13
15
 
14
- run_migration do
15
- create_table(:users, force: true) do |t|
16
- t.string :email
17
- t.string :name
18
- t.timestamps
16
+ run_migration do
17
+ create_table(:users, force: true) do |t|
18
+ t.string :email
19
+ t.string :name
20
+ t.timestamps
21
+ end
22
+ end
19
23
  end
20
- end
21
- end
22
24
 
23
- describe :InstanceMethods do
24
- describe :assign_attributes do
25
25
  let(:assign_attributes!) { entity.assign_attributes attributes, context: context }
26
26
 
27
27
  context 'with :create context' do
28
28
  let(:entity) { UserEntity.new }
29
- let(:attributes) { { email: 'remi@example.com' } }
29
+ let(:attributes) { { email: 'art@vandelay.com' } }
30
30
  let(:context) { :create }
31
31
 
32
32
  before { assign_attributes! }
33
33
 
34
- it { expect(entity.object.email).to eql 'remi@example.com' }
34
+ it { expect(entity.object.email).to eql 'art@vandelay.com' }
35
35
  it { expect(entity.object.name).to be_blank }
36
36
  it { expect(entity.object.created_at).to be_blank }
37
37
  it { expect(entity.object.updated_at).to be_blank }
38
38
  end
39
39
 
40
- context 'with :update context', focus: true do
40
+ context 'with :update context' do
41
41
  let(:entity) { UserEntity.new(User.first) }
42
42
  let(:context) { :update }
43
- let(:create_user!) { User.create(email: 'remi@example.com', name: 'Rémi Prévost') }
43
+ let(:create_user!) { User.create(email: 'art@vandelay.com', name: 'Art Vandelay') }
44
44
 
45
45
  before { create_user! }
46
46
 
47
47
  context 'when updating valid attributes' do
48
- let(:attributes) { { email: 'remi+test@example.com' } }
48
+ let(:attributes) { { email: 'art+test@vandelay.com' } }
49
49
  before { assign_attributes! }
50
50
 
51
- it { expect(entity.object.email).to eql 'remi+test@example.com' }
51
+ it { expect(entity.object.email).to eql 'art+test@vandelay.com' }
52
52
  it { expect(entity.object.name).to be_blank }
53
53
  it { expect(entity.object.created_at).to_not be_blank }
54
54
  it { expect(entity.object.updated_at).to_not be_blank }
55
55
  end
56
56
 
57
57
  context 'when updating a protected attribute' do
58
- let(:attributes) { { email: 'remi+test@example.com', created_at: '2013-08-29 21:22:44' } }
58
+ let(:attributes) { { email: 'art+test@vandelay.com', created_at: '2013-08-29 21:22:44' } }
59
59
 
60
- it { expect { assign_attributes! }.to raise_error(Encore::Entity::Input::ProtectedAttributeError) }
60
+ it { expect { assign_attributes! }.to raise_error(Encore::Entity::Input::InvalidAttributeError) }
61
61
  end
62
62
  end
63
63
 
64
64
  context 'with :partial_update context' do
65
65
  let(:entity) { UserEntity.new(User.first) }
66
- let(:attributes) { { email: 'remi+test@example.com' } }
66
+ let(:attributes) { { email: 'art+test@vandelay.com' } }
67
67
  let(:context) { :partial_update }
68
- let(:create_user!) { User.create(email: 'remi@example.com', name: 'Rémi Prévost') }
68
+ let(:create_user!) { User.create(email: 'art@vandelay.com', name: 'Art Vandelay') }
69
69
 
70
70
  before do
71
71
  create_user!
72
72
  assign_attributes!
73
73
  end
74
74
 
75
- it { expect(entity.object.email).to eql 'remi+test@example.com' }
76
- it { expect(entity.object.name).to eql 'Rémi Prévost' }
75
+ it { expect(entity.object.email).to eql 'art+test@vandelay.com' }
76
+ it { expect(entity.object.name).to eql 'Art Vandelay' }
77
77
  it { expect(entity.object.created_at).to_not be_blank }
78
78
  it { expect(entity.object.updated_at).to_not be_blank }
79
79
  end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Entity::Output::JSON do
4
+ describe :InstanceMethods do
5
+ describe :as_json do
6
+ context 'for simple entity' do
7
+ before do
8
+ # Create a model
9
+ spawn_model :User
10
+
11
+ # Create an entity
12
+ spawn_entity :UserEntity do
13
+ expose :name
14
+ end
15
+
16
+ run_migration do
17
+ create_table(:users, force: true) do |t|
18
+ t.string :name
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:user) { User.create(name: 'Art Vandelay') }
24
+ let(:entity) { UserEntity.new(user) }
25
+ let(:json) { entity.as_json }
26
+
27
+ it { expect(json.keys).to include :id, :name }
28
+ it { expect(json[:id]).to eql user.id }
29
+ it { expect(json[:name]).to eql 'Art Vandelay' }
30
+ end
31
+
32
+ context 'for entity with associations' do
33
+ before do
34
+ # Create a model
35
+ spawn_model :User do
36
+ has_many :comments
37
+ end
38
+
39
+ # Create a model
40
+ spawn_model :Comment
41
+
42
+ # Create an entity
43
+ spawn_entity :UserEntity do
44
+ expose :name
45
+ expose_many :comments
46
+ end
47
+
48
+ run_migration do
49
+ create_table(:users, force: true) do |t|
50
+ t.string :name
51
+ end
52
+
53
+ create_table(:comments, force: true) do |t|
54
+ t.text :body
55
+ t.integer :user_id
56
+ end
57
+ end
58
+ end
59
+
60
+ let(:user) do
61
+ User.create(name: 'Art Vandelay').tap do |user|
62
+ user.comments << Comment.create(body: 'Test 1')
63
+ user.comments << Comment.create(body: 'Test 2')
64
+ end
65
+ end
66
+
67
+ let(:entity) { UserEntity.new(user) }
68
+ let(:json) { entity.as_json }
69
+
70
+ it { expect(json.keys).to include :id, :name, :comments }
71
+ it { expect(json[:comments]).to be_instance_of Array }
72
+ it { expect(json[:comments]).to include *Comment.pluck(:id) }
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Entity::Output do
4
+ # Nothing to test here
5
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Entity do
4
+ describe :InstanceMethods do
5
+ describe :method_missing do
6
+ before do
7
+ spawn_model :User
8
+ spawn_entity(:UserEntity) { expose :name }
9
+
10
+ run_migration do
11
+ create_table(:users, force: true) do |t|
12
+ t.string :name
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:user) { User.create }
18
+ let(:entity) { UserEntity.new(user) }
19
+
20
+ context 'for delegated method' do
21
+ before { expect(user).to receive(:save) }
22
+ specify { entity.save }
23
+ end
24
+
25
+ context 'for setter method' do
26
+ before { expect(user).to receive(:name=).with('Art Vandelay') }
27
+ specify { entity.name = "Art Vandelay" }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Encore::Helpers::ControllerHelper do
4
+ describe :process! do
5
+ before do
6
+ # Create a model
7
+ spawn_model :User
8
+
9
+ # Create a controller
10
+ spawn_controller :UsersController do
11
+ before_filter :fetch_user, only: [:update]
12
+
13
+ define_method :create do
14
+ @entity = UserEntity.new
15
+ @entity.assign_attributes(params[:user], context: :create)
16
+ process! @entity
17
+ end
18
+
19
+ define_method :update do
20
+ @entity = UserEntity.new(@user)
21
+ @entity.assign_attributes(params[:user], context: :update0)
22
+ process! @entity
23
+ end
24
+
25
+ define_method :fetch_user do
26
+ @user = User.find(params[:id])
27
+ end
28
+ end
29
+
30
+ # Create an entity
31
+ spawn_entity :UserEntity do
32
+ expose :name
33
+ end
34
+
35
+ run_migration do
36
+ create_table(:users, force: true) do |t|
37
+ t.string :name
38
+ end
39
+ end
40
+ end
41
+
42
+ let(:request) { ActionDispatch::TestRequest.new }
43
+ let(:env) { request.env.tap { |e| e['action_dispatch.request.parameters'] = params } }
44
+
45
+ context 'for create context' do
46
+ let(:params) { { user: { name: 'H.E. Pennypacker' } } }
47
+ let(:response) { UsersController.action(:create).call(env) }
48
+ it { expect(response.first).to eql 201 }
49
+ end
50
+
51
+ context 'for update context' do
52
+ before { User.create(name: 'Art Vandelay') }
53
+
54
+ let(:params) { { id: 1, user: { name: 'H.E. Pennypacker' } } }
55
+ let(:response) { UsersController.action(:update).call(env) }
56
+ it { expect(response.first).to eql 200 }
57
+ end
58
+ end
59
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,31 @@
1
1
  $:.unshift File.expand_path('../lib', __FILE__)
2
2
 
3
+ # Test dependencies
3
4
  require 'rspec'
4
5
  require 'sqlite3'
5
6
 
7
+ # Data structure
8
+ require 'active_record'
9
+ require 'ostruct'
10
+
11
+ # Encore
6
12
  require 'encore'
7
13
 
8
14
  # Require our macros and extensions
9
15
  Dir[File.expand_path('../../spec/support/macros/*.rb', __FILE__)].map(&method(:require))
10
16
 
17
+ # Emulate our railtie's behavior
18
+ require 'encore/railtie'
19
+ Encore::Helpers::ControllerHelper.inject_into_action_controller
20
+
11
21
  RSpec.configure do |config|
12
22
  # Include our macros
13
23
  config.include DatabaseMacros
14
24
  config.include ModelMacros
15
25
 
26
+ # Shared examples aliases
27
+ config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
28
+
16
29
  config.before(:each) do
17
30
  # Create the SQLite database
18
31
  setup_database
@@ -14,6 +14,20 @@ module ModelMacros
14
14
  end
15
15
  end
16
16
 
17
+ # Create a new OpenStruct struct
18
+ def spawn_struct(klass_name, &block)
19
+ spawn_klass klass_name, OpenStruct do
20
+ instance_exec(&block) if block
21
+ end
22
+ end
23
+
24
+ # Create a new ActionPack controller
25
+ def spawn_controller(klass_name, &block)
26
+ spawn_klass klass_name, ActionController::Base do
27
+ instance_exec(&block) if block
28
+ end
29
+ end
30
+
17
31
  protected
18
32
 
19
33
  # Create a new class
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: encore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémi Prévost
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-30 00:00:00.000000000 Z
11
+ date: 2013-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,13 +67,13 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: activerecord
70
+ name: rails
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: 3.0.0
76
- type: :runtime
76
+ type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
@@ -94,7 +94,7 @@ dependencies:
94
94
  - - '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: 3.0.0
97
- description: Encore is a framework built on top of ActiveRecord to manage entities.
97
+ description: Encore is a Ruby framework to help dealing with entities.
98
98
  email:
99
99
  - rprevost@mirego.com
100
100
  executables: []
@@ -103,24 +103,41 @@ extra_rdoc_files: []
103
103
  files:
104
104
  - .gitignore
105
105
  - .rspec
106
+ - .travis.yml
106
107
  - Gemfile
107
108
  - LICENSE.md
108
109
  - README.md
109
110
  - Rakefile
110
111
  - encore.gemspec
112
+ - gemfiles/Gemfile.activerecord-3.2.x
113
+ - gemfiles/Gemfile.activerecord-4.0
111
114
  - lib/encore.rb
115
+ - lib/encore/association.rb
116
+ - lib/encore/association/has_many_association.rb
117
+ - lib/encore/association/has_one_association.rb
112
118
  - lib/encore/attribute.rb
113
119
  - lib/encore/entity.rb
114
120
  - lib/encore/entity/input.rb
121
+ - lib/encore/entity/input/associations.rb
115
122
  - lib/encore/entity/input/errors.rb
116
123
  - lib/encore/entity/input/exposed_attributes.rb
117
124
  - lib/encore/entity/output.rb
125
+ - lib/encore/entity/output/json.rb
126
+ - lib/encore/helpers.rb
127
+ - lib/encore/helpers/controller_helper.rb
128
+ - lib/encore/railtie.rb
118
129
  - lib/encore/version.rb
119
- - spec/poutine/entity_spec.rb
130
+ - spec/encore/entity/input/associations_spec.rb
131
+ - spec/encore/entity/input/exposed_attributes_spec.rb
132
+ - spec/encore/entity/input_spec.rb
133
+ - spec/encore/entity/output/json_spec.rb
134
+ - spec/encore/entity/output_spec.rb
135
+ - spec/encore/entity_spec.rb
136
+ - spec/encore/helpers/controller_helper_spec.rb
120
137
  - spec/spec_helper.rb
121
138
  - spec/support/macros/database_macros.rb
122
139
  - spec/support/macros/model_macros.rb
123
- homepage: https://github.com/remiprev/encore
140
+ homepage: https://github.com/mirego/encore
124
141
  licenses:
125
142
  - BSD 3-Clause
126
143
  metadata: {}
@@ -143,9 +160,15 @@ rubyforge_project:
143
160
  rubygems_version: 2.0.2
144
161
  signing_key:
145
162
  specification_version: 4
146
- summary: Encore is a framework built on top of ActiveRecord to manage entities.
163
+ summary: Encore is a Ruby framework to help dealing with entities.
147
164
  test_files:
148
- - spec/poutine/entity_spec.rb
165
+ - spec/encore/entity/input/associations_spec.rb
166
+ - spec/encore/entity/input/exposed_attributes_spec.rb
167
+ - spec/encore/entity/input_spec.rb
168
+ - spec/encore/entity/output/json_spec.rb
169
+ - spec/encore/entity/output_spec.rb
170
+ - spec/encore/entity_spec.rb
171
+ - spec/encore/helpers/controller_helper_spec.rb
149
172
  - spec/spec_helper.rb
150
173
  - spec/support/macros/database_macros.rb
151
174
  - spec/support/macros/model_macros.rb