sunstone 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ require 'sunstone/type/value'
2
+ require 'sunstone/type/mutable'
3
+ require 'sunstone/type/boolean'
4
+ require 'sunstone/type/date_time'
5
+ require 'sunstone/type/decimal'
6
+ require 'sunstone/type/integer'
7
+ require 'sunstone/type/string'
8
+
9
+ module Sunstone
10
+ class Schema
11
+
12
+ attr_accessor :attributes
13
+
14
+ def initialize
15
+ @attributes = {}
16
+ end
17
+
18
+ def attribute(name, type, options = {})
19
+ types = Sunstone::Type::Value.subclasses
20
+ type = types.find {|sc| sc.name.demodulize.downcase == type.to_s}
21
+
22
+ @attributes[name.to_s] = type.new(options)
23
+ end
24
+
25
+ def [](name)
26
+ @attributes[name.to_s]
27
+ end
28
+
29
+ Sunstone::Type::Value.subclasses.each do |type|
30
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
31
+ def #{type.name.demodulize.downcase}(name, options = {})
32
+ attribute(name, "#{type.name.demodulize.downcase}", options)
33
+ end
34
+ EOV
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module Sunstone
2
+ module Type
3
+ class Boolean < Value
4
+
5
+ TRUE_VALUES = [true, 'true', 'TRUE'].to_set
6
+
7
+ private
8
+
9
+ def _cast_value(value)
10
+ if value == ''
11
+ nil
12
+ else
13
+ TRUE_VALUES.include?(value)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module Sunstone
2
+ module Type
3
+ class DateTime < Value
4
+
5
+ private
6
+
7
+ def _type_cast_for_json(value)
8
+ value.iso8601(3) if value
9
+ end
10
+
11
+ def _cast_value(string)
12
+ return string unless string.is_a?(::String)
13
+ return if string.empty?
14
+
15
+ ::DateTime.iso8601(string) || ::DateTime.parse(string)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Sunstone
2
+ module Type
3
+ class Decimal < Value
4
+
5
+ private
6
+
7
+ def _cast_value(value)
8
+ if value == ''
9
+ nil
10
+ elsif value.is_a?(BigDecimal)
11
+ value
12
+ else
13
+ BigDecimal.new(value.to_s)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Sunstone
2
+ module Type
3
+ class Integer < Value
4
+
5
+ private
6
+
7
+ def _cast_value(value)
8
+ case value
9
+ when true then 1
10
+ when false then 0
11
+ else value.to_i
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Sunstone
2
+ module Type
3
+ module Mutable # :nodoc:
4
+ def type_cast_from_user(value)
5
+ type_cast_from_json(type_cast_for_json(value))
6
+ end
7
+
8
+ # +raw_old_value+ will be the `_before_type_cast` version of the
9
+ # value (likely a string). +new_value+ will be the current, type
10
+ # cast value.
11
+ def changed_in_place?(raw_old_value, new_value)
12
+ raw_old_value != type_cast_for_json(new_value)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Sunstone
2
+ module Type
3
+ class String < Value
4
+
5
+ private
6
+
7
+ def _cast_value(value)
8
+ case value
9
+ when true then "1"
10
+ when false then "0"
11
+ # Dup the string
12
+ else ::String.new(value.to_s)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,97 @@
1
+ module Sunstone
2
+ module Type
3
+ class Value
4
+
5
+ attr_accessor :options
6
+
7
+ def initialize(options={})
8
+ @options = options
9
+ end
10
+
11
+ def type_cast_from_json(value)
12
+ if @options[:array]
13
+ value.nil? ? nil : value.map{ |v| _type_cast_from_json(v) }
14
+ else
15
+ _type_cast_from_json(value)
16
+ end
17
+ end
18
+
19
+ def type_cast_from_user(value)
20
+ if @options[:array]
21
+ value.nil? ? nil : value.map{ |v| _type_cast_from_user(v) }
22
+ else
23
+ _type_cast_from_user(value)
24
+ end
25
+ end
26
+
27
+ def type_cast_for_json(value)
28
+ if @options[:array]
29
+ value.nil? ? nil : value.map{ |v| _type_cast_for_json(v) }
30
+ else
31
+ _type_cast_for_json(value)
32
+ end
33
+ end
34
+
35
+ # Determines whether a value has changed for dirty checking. +old_value+
36
+ # and +new_value+ will always be type-cast. Types should not need to
37
+ # override this method.
38
+ def changed?(old_value, new_value, _new_value_before_type_cast)
39
+ old_value != new_value
40
+ end
41
+
42
+ # Determines whether the mutable value has been modified since it was
43
+ # read. Returns +false+ by default. This method should not need to be
44
+ # overriden directly. Types which return a mutable value should include
45
+ # +Type::Mutable+, which will define this method.
46
+ def changed_in_place?(*)
47
+ false
48
+ end
49
+
50
+ def readonly?
51
+ @options[:readonly]
52
+ end
53
+
54
+ private
55
+
56
+ # Type casts a value from json into the appropriate ruby type. Classes
57
+ # which do not need separate type casting behavior for json and user
58
+ # provided values should override +_cast_value+ instead.
59
+ def _type_cast_from_json(value)
60
+ _type_cast(value)
61
+ end
62
+
63
+ # Type casts a value from user input (e.g. from a setter). This value may
64
+ # be a string from the form builder, or an already type cast value
65
+ # provided manually to a setter.
66
+ #
67
+ # Classes which do not need separate type casting behavior for json
68
+ # and user provided values should override +_type_cast+ or +_cast_value+
69
+ # instead.
70
+ def _type_cast_from_user(value)
71
+ _type_cast(value)
72
+ end
73
+
74
+ # Cast a value from the ruby type to a type that the json knows how
75
+ # to understand. The returned value from this method should be a
76
+ # +String+, +Numeric+, +Symbol+, +true+, +false+, or +nil+
77
+ def _type_cast_for_json(value)
78
+ value
79
+ end
80
+
81
+ def _type_cast(value)
82
+ _cast_value(value) unless value.nil?
83
+ end
84
+
85
+ # Convenience method for types which do not need separate type casting
86
+ # behavior for user and database inputs. Called by
87
+ # `_type_cast_from_json` and `_type_cast_from_user` for all values except
88
+ # `nil`.
89
+ #
90
+ # If you wish to catch the nil case use the `_type_cast` function
91
+ def _cast_value(value) # :doc:
92
+ value
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "sunstone"
3
+ s.version = '0.1.0'
4
+ s.authors = ["Jon Bracy"]
5
+ s.email = ["jonbracy@gmail.com"]
6
+ s.homepage = "http://sunstonerb.com"
7
+ s.summary = %q{A library for interacting with REST APIs}
8
+ s.description = %q{A library for interacting with REST APIs. Similar to ActiveResource}
9
+
10
+ s.files = `git ls-files`.split("\n")
11
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
13
+ s.require_paths = ["lib"]
14
+
15
+ # Developoment
16
+ s.add_development_dependency 'rake'
17
+ s.add_development_dependency 'rdoc'
18
+ s.add_development_dependency 'sdoc'
19
+ s.add_development_dependency 'bundler'
20
+ s.add_development_dependency 'minitest'
21
+ s.add_development_dependency 'minitest-reporters'
22
+ s.add_development_dependency 'mocha'
23
+ s.add_development_dependency 'faker'
24
+ s.add_development_dependency 'factory_girl'
25
+ s.add_development_dependency 'webmock'
26
+ s.add_development_dependency 'sdoc-templates-42floors'
27
+
28
+ # Runtime
29
+ s.add_runtime_dependency 'wankel'
30
+ s.add_runtime_dependency 'cookie_store'
31
+ s.add_runtime_dependency 'activesupport'
32
+ s.add_runtime_dependency 'activemodel'
33
+ s.add_runtime_dependency 'connection_pool'
34
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ class BelongsToModel < Sunstone::Model
4
+ end
5
+
6
+
7
+ class Sunstone::Model::AssociationsTest < Minitest::Test
8
+
9
+ def setup
10
+ @klass = Class.new(Sunstone::Model)
11
+ end
12
+
13
+ test "inherited models get a schema setup with id" do
14
+ assert_equal({}, @klass.reflect_on_associations)
15
+ end
16
+
17
+ test "::belongs_to adds association" do
18
+ @klass.belongs_to :belongs_to_model
19
+
20
+ assert_equal({
21
+ :name=>:belongs_to_model,
22
+ :macro=>:belongs_to,
23
+ :klass=>BelongsToModel,
24
+ :foreign_key=>:belongs_to_model_id
25
+ }, @klass.reflect_on_associations[:belongs_to_model])
26
+ end
27
+
28
+ test "::belongs_to addes foreign_key attribute to class" do
29
+ @klass.belongs_to :belongs_to_model
30
+
31
+ assert_kind_of Sunstone::Type::Integer, @klass.schema[:belongs_to_model_id]
32
+ end
33
+
34
+ test "::belongs_to setter and getter" do
35
+ @klass.belongs_to :belongs_to_model
36
+
37
+ model = @klass.new
38
+ omodel = BelongsToModel.new(:id => 10)
39
+ assert_nil model.belongs_to_model
40
+ model.belongs_to_model = omodel
41
+ assert_equal omodel, model.belongs_to_model
42
+ assert_equal 10, model.belongs_to_model_id
43
+ end
44
+
45
+ test "::has_many adds association" do
46
+ @klass.has_many :belongs_to_models
47
+
48
+ assert_equal({
49
+ :name => :belongs_to_models,
50
+ :macro => :has_many,
51
+ :klass => BelongsToModel
52
+ }, @klass.reflect_on_associations[:belongs_to_models])
53
+ end
54
+
55
+ end
@@ -0,0 +1,60 @@
1
+ require 'test_helper'
2
+
3
+ class Sunstone::Model::AttributesTest < Minitest::Test
4
+
5
+ def setup
6
+ @klass = Class.new(Sunstone::Model)
7
+ end
8
+
9
+ test "inherited models get a schema setup with id" do
10
+ assert_kind_of Sunstone::Schema, @klass.schema
11
+ assert_kind_of Sunstone::Type::Integer, @klass.schema[:id]
12
+ end
13
+
14
+ test "::attribute" do
15
+ @klass.attribute :name, :string
16
+
17
+ assert_kind_of Sunstone::Type::String, @klass.schema[:name]
18
+ end
19
+
20
+ test "::attribute sets up readers" do
21
+ @klass.attribute :name, :string
22
+ model = @klass.new
23
+
24
+ model.attributes[:name] = "my name"
25
+ assert_equal "my name", model.name
26
+ end
27
+
28
+ test "::attributes sets up writer" do
29
+ @klass.attribute :name, :string
30
+ model = @klass.new
31
+
32
+ model.schema[:name].expects(:type_cast_from_user).with("my name").returns("value")
33
+ model.name = "my name"
34
+
35
+ assert_equal "value", model.name
36
+ assert_equal "value", model.attributes[:name]
37
+ end
38
+
39
+ test "::define_schema" do
40
+ @klass.define_schema do
41
+ attribute :name, :string
42
+ integer :size
43
+ end
44
+
45
+ assert_kind_of Sunstone::Type::String, @klass.schema[:name]
46
+ assert_kind_of Sunstone::Type::Integer, @klass.schema[:size]
47
+ end
48
+
49
+ test '#has?' do
50
+ @klass.define_schema do
51
+ attribute :name, :string
52
+ integer :size
53
+ end
54
+ model = @klass.new(:size => 20)
55
+
56
+ assert_equal false, model.has?(:name)
57
+ assert_equal true, model.has?(:size)
58
+ end
59
+
60
+ end
@@ -0,0 +1,173 @@
1
+ require 'test_helper'
2
+
3
+ class Sunstone::Model::PersistenceTest < Minitest::Test
4
+
5
+ class TestModel < Sunstone::Model
6
+ define_schema do
7
+ attribute :name, :string
8
+ integer :size
9
+ datetime :timestamp
10
+ datetime :updated_at, :readonly => true
11
+ end
12
+ end
13
+
14
+ def setup
15
+ Sunstone.site = "http://test_api_key@testhost.com"
16
+ @klass = Class.new(Sunstone::Model)
17
+ end
18
+
19
+ test '#serialize' do
20
+ time = Time.now
21
+ model = TestModel.new(:size => 20, :timestamp => time)
22
+ assert_equal '{"id":null,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":null}', model.serialize
23
+ end
24
+
25
+ test '#serialize(:only => [KEYS])' do
26
+ time = Time.now
27
+ model = TestModel.new(:size => 20, :timestamp => time)
28
+
29
+ assert_equal '{"size":20}', model.serialize(:only => ['size'])
30
+ end
31
+
32
+
33
+
34
+
35
+ test '#save a new record' do
36
+ time = Time.now
37
+ model = TestModel.new(:size => 20, :timestamp => time)
38
+
39
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
40
+ .with(
41
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
42
+ .to_return(
43
+ :status => 200,
44
+ :body => '{"id":20,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
45
+
46
+ assert_equal true, model.save
47
+ end
48
+
49
+ test '#save a new invalid record' do
50
+ time = Time.now
51
+ model = TestModel.new(:size => 20, :timestamp => time)
52
+
53
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
54
+ .with(
55
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
56
+ .to_return(
57
+ :status => 400,
58
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
59
+
60
+ assert_equal false, model.save
61
+ end
62
+
63
+ test "#save a new record updates the model with the response" do
64
+ time = Time.now
65
+ model = TestModel.new(:size => 20, :timestamp => time)
66
+
67
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
68
+ .with(
69
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
70
+ .to_return(
71
+ :status => 200,
72
+ :body => '{"id":20,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
73
+
74
+ model.save
75
+ assert_equal 20, model.id
76
+ assert_equal time.iso8601(3), model.updated_at.iso8601(3)
77
+ end
78
+
79
+ test "#save on an new record will set new_record? is false" do
80
+ time = Time.now
81
+ model = TestModel.new(:size => 20, :timestamp => time)
82
+
83
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
84
+ .with(
85
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
86
+ .to_return(
87
+ :status => 200,
88
+ :body => '{"id":20,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
89
+
90
+ model.save
91
+ assert_equal false, model.new_record?
92
+ end
93
+
94
+ test '#save on a persisted record' do
95
+ time = Time.now
96
+ model = TestModel.new(:id => 13, :size => 20, :timestamp => time)
97
+ model.instance_variable_set(:@new_record, false)
98
+
99
+ stub_request(:put, "http://testhost.com/sunstone_model_persistence_test_test_models/13")
100
+ .with(
101
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
102
+ .to_return(
103
+ :status => 200,
104
+ :body => '{"id":13,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
105
+
106
+ assert_equal true, model.save
107
+ end
108
+
109
+ test '#save on a persisted record updates the model with the response' do
110
+ time = Time.now
111
+ model = TestModel.new(:id => 13, :size => 20, :timestamp => time)
112
+ model.instance_variable_set(:@new_record, false)
113
+
114
+ stub_request(:put, "http://testhost.com/sunstone_model_persistence_test_test_models/13")
115
+ .with(
116
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
117
+ .to_return(
118
+ :status => 200,
119
+ :body => '{"id":13,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
120
+
121
+ model.save
122
+ assert_equal 13, model.id
123
+ assert_equal time.iso8601(3), model.updated_at.iso8601(3)
124
+ end
125
+
126
+ test '#save! a new record' do
127
+ time = Time.now
128
+ model = TestModel.new(:size => 20, :timestamp => time)
129
+
130
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
131
+ .with(
132
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
133
+ .to_return(
134
+ :status => 200,
135
+ :body => '{"id":20,"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
136
+
137
+ assert_equal true, model.save!
138
+ end
139
+
140
+ test '#save! a new invalid record' do
141
+ time = Time.now
142
+ model = TestModel.new(:size => 20, :timestamp => time)
143
+
144
+ stub_request(:post, "http://testhost.com/sunstone_model_persistence_test_test_models")
145
+ .with(
146
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '"}')
147
+ .to_return(
148
+ :status => 400,
149
+ :body => '{"name":null,"size":20,"timestamp":"' + time.iso8601(3) + '","updated_at":"' + time.iso8601(3) + '"}')
150
+
151
+ assert_raises Sunstone::RecordInvalid do
152
+ model.save!
153
+ end
154
+ end
155
+
156
+
157
+ test '::find(id)' do
158
+ stub_request(:get, "http://testhost.com/sunstone_model_persistence_test_test_models/324").to_return(:body => '{"size": 40}')
159
+
160
+ model = TestModel.find('324')
161
+ assert_kind_of TestModel, model
162
+ assert_equal 40, model.size
163
+ end
164
+
165
+ test '::find(id) with 404' do
166
+ stub_request(:get, "http://testhost.com/sunstone_model_persistence_test_test_models/324").to_return(:status => 404)
167
+
168
+ assert_raises Sunstone::Exception::NotFound do
169
+ TestModel.find('324')
170
+ end
171
+ end
172
+
173
+ end