encore 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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