dymos 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: 408c57bc4b5bc1b6a83e14f53cb1e69071e885b0
4
- data.tar.gz: eaf61ec14a52d888980e478d45aa91180bdd0536
3
+ metadata.gz: 7c0c0a46aa4fd1522baccf359d95557969ef7b84
4
+ data.tar.gz: ce4dddfca46a0a4531633111ff41221821307972
5
5
  SHA512:
6
- metadata.gz: bd4d7dbab18afeba617102abe889732658458aac36b119b5c5f5d7c5b5a0288d44040f27ca437dbfdf079610e3e719280dcdee59f16c3a490cdc374c704382a9
7
- data.tar.gz: b4f0eafab6f9c8346c2d986a189f7038b5cc8f67534289eebb1aa1a1ea45cef7d47b8178529ce8b54ae491195fdc794d17d62ecd3c81240547a4ae31fe883bf0
6
+ metadata.gz: 51158e3ba9bbe24135f76217f825fa11282bfce214f993ed2a92a1b01de54ed0d554137add7b387658dfd287e2dba741dde117a3528fb59012756d17adef4807
7
+ data.tar.gz: 4c654ea7370834f50eff5f83e7562ec77ba68d595a8fa0d26b6cd30898d5c674974f5ff863a45351a58e9d47504dfefef98f8f84709c0dbd7d687fe9aaa33e59
data/.gitignore CHANGED
@@ -13,4 +13,5 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  .idea/
16
- /vendor/bundle
16
+ /vendor/bundle
17
+ .ruby-version
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
- --warnings
3
2
  --require spec_helper
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- before_script: 'bundle exec fake_dynamo -d .fake_dynamo.db -P .fake_dynamo.pid -D'
4
- script: 'bundle exec rake'
5
- after_script: 'kill `cat .fake_dynamo.pid`'
3
+ before_install:
4
+ - wget http://dynamodb-local.s3-website-us-west-2.amazonaws.com/dynamodb_local_latest
5
+ - tar xfz dynamodb_local_latest
6
+ - java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -inMemory -port 4567 &
6
7
  rvm:
7
8
  - 2.1.0
8
9
  - 2.1.1
data/Rakefile CHANGED
@@ -1,13 +1,7 @@
1
- # -*- encoding: utf-8 -*-
2
- require "bundler/gem_tasks"
3
- require 'rubygems'
4
- require 'rake'
5
- $:.unshift File.join(File.dirname(__FILE__), "lib")
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
6
3
 
7
- require 'rspec/core'
4
+ # RSpec
8
5
  require 'rspec/core/rake_task'
9
-
10
- task :default => :spec
11
-
12
- desc "Run all specs in spec directory"
13
6
  RSpec::Core::RakeTask.new(:spec)
7
+ task :default => :spec
@@ -1,8 +1,14 @@
1
+ require "dymos/query/attribute"
2
+ require "dymos/query/expect"
3
+ require "dymos/query/builder"
4
+ require "dymos/query/put_item"
5
+ require "dymos/query/update_item"
6
+ require "dymos/query/get_item"
7
+ require "dymos/query/query"
8
+ require "dymos/attribute"
9
+ require "dymos/command"
1
10
  require "dymos/model"
2
11
  require "dymos/version"
3
12
 
4
13
  module Dymos
5
- def self.model
6
- Dymos::Model
7
- end
8
14
  end
@@ -1,5 +1,4 @@
1
1
  module Dymos
2
2
  module Attribute
3
-
4
3
  end
5
4
  end
@@ -0,0 +1,32 @@
1
+ module Dymos
2
+ module Command
3
+
4
+ # @return [PutItem]
5
+ def put
6
+ Dymos::Query::PutItem.new(:put_item, table_name, class_name)
7
+ end
8
+
9
+ # @return [UpdateItem]
10
+ def update
11
+ Dymos::Query::UpdateItem.new(:update_item, table_name, class_name)
12
+ end
13
+
14
+ # @return [GetItem]
15
+ def get
16
+ Dymos::Query::GetItem.new(:get_item, table_name, class_name)
17
+ end
18
+
19
+ # @return [Query]
20
+ def query
21
+ Dymos::Query::Query.new(:query, table_name, class_name)
22
+ end
23
+
24
+ def scan
25
+
26
+ end
27
+
28
+ def describe
29
+
30
+ end
31
+ end
32
+ end
@@ -4,6 +4,7 @@ require 'active_model'
4
4
  module Dymos
5
5
  class Model
6
6
  include ActiveModel::Model
7
+ extend Dymos::Command
7
8
 
8
9
  def initialize(params={})
9
10
  @attributes = {}
@@ -18,6 +19,7 @@ module Dymos
18
19
  end
19
20
 
20
21
  def self.table(name)
22
+ define_singleton_method('table_name') { name }
21
23
  define_method('table_name') { name }
22
24
  end
23
25
 
@@ -89,10 +91,15 @@ module Dymos
89
91
  end
90
92
 
91
93
  def dynamo
92
-
93
94
  @client ||= Aws::DynamoDB::Client.new
94
95
  end
95
96
 
97
+ # @return [String]
98
+ def self.class_name
99
+ self.name
100
+ end
101
+
102
+ # @return [String]
96
103
  def class_name
97
104
  self.class.name
98
105
  end
@@ -0,0 +1,14 @@
1
+ module Dymos
2
+ module Query
3
+ class Attribute
4
+ def initialize(value)
5
+ value =value.to_i if /^[0-9]+$/ =~ value
6
+ @value = value
7
+ end
8
+
9
+ def data
10
+ @value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ module Dymos
2
+ module Query
3
+ class Builder
4
+ attr_accessor :table_name, :command, :query
5
+
6
+ def initialize(command, table_name=nil, class_name=nil)
7
+ @class_name = class_name
8
+ @command = command
9
+ @table_name = table_name if table_name.present?
10
+ self
11
+ end
12
+
13
+ def query
14
+
15
+ end
16
+
17
+ def execute(client)
18
+ begin
19
+ res = client.send command, query
20
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
21
+ return false
22
+ end
23
+ if @class_name.present?
24
+ if res.data.respond_to? :items
25
+ res.data[:items].map do |datum|
26
+ obj = Object.const_get(@class_name).new
27
+ obj.attributes = datum
28
+ obj
29
+ end
30
+ elsif res.data.respond_to? :attributes
31
+ return nil if res.attributes.nil?
32
+ obj = Object.const_get(@class_name).new
33
+ obj.attributes = res.attributes
34
+ obj
35
+ end
36
+ else
37
+ res.data
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,65 @@
1
+ module Dymos
2
+ module Query
3
+ class Expect < Attribute
4
+ def initialize
5
+ end
6
+
7
+ def condition(operator, value)
8
+ if value.present?
9
+ value1, value2 = value.split(" ")
10
+ value1 =value1.to_i if /^[0-9]+$/.match(value1)
11
+ value2 =value2.to_i if /^[0-9]+$/.match(value2)
12
+ @value = value1
13
+ end
14
+
15
+ case operator.to_sym
16
+ when :==, :eq
17
+ @operator = 'EQ'
18
+ when :!=, :nq
19
+ @operator = 'NE'
20
+ when :<=, :le
21
+ @operator = 'LE'
22
+ when :<, :lt
23
+ @operator = 'LT'
24
+ when :>=, :ge
25
+ @operator = 'GE'
26
+ when :>, :gt
27
+ @operator = 'GT'
28
+ when :between
29
+ @operator = 'BETWEEN'
30
+ @value =[value1, value2]
31
+ when :is_null
32
+ @operator = 'NULL'
33
+ when :is_not_null
34
+ @operator = 'NOT_NULL'
35
+ when :contains
36
+ @operator = 'CONTAINS'
37
+ when :not_contains
38
+ @operator = 'NOT_CONTAINS'
39
+ when :begins_with
40
+ @operator = 'BEGINS_WITH'
41
+ else
42
+ raise ArgumentError, '%s is not defined ' % operator
43
+ end
44
+ self
45
+ end
46
+
47
+ def data(is_force_array=false)
48
+ value = super()
49
+ if is_force_array or value.is_a? Array
50
+ data = {
51
+ attribute_value_list: (is_force_array and !(value.is_a? Array)) ? [value] : value,
52
+ comparison_operator: @operator
53
+ }
54
+ else
55
+ data = {}
56
+ data[:value]= value if value.present?
57
+ data[:comparison_operator]= @operator
58
+ end
59
+
60
+ data[:exists] = true if @exists.present?
61
+ data
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,19 @@
1
+ module Dymos
2
+ module Query
3
+ class GetItem < ::Dymos::Query::Builder
4
+
5
+ def key(params)
6
+ @key = params
7
+ self
8
+ end
9
+
10
+ def query
11
+ {
12
+ table_name: @table_name.to_s,
13
+ key: @key,
14
+ consistent_read: true,
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ module Dymos
2
+ module Query
3
+ class PutItem < ::Dymos::Query::Builder
4
+
5
+ def item(params)
6
+ @item = params
7
+ self
8
+ end
9
+
10
+ def expected(params)
11
+ @expected = Hash[params.map do |name, expression|
12
+ operator, values = expression.split(' ', 2)
13
+ if values.nil?
14
+ value1 = operator
15
+ [name, ::Dymos::Query::Expect.new.condition(operator, nil).data]
16
+ else
17
+ value1, value2 = values.split(' ')
18
+ if value2.present?
19
+ [name, ::Dymos::Query::Expect.new.condition(operator, values).data]
20
+ else
21
+ [name, ::Dymos::Query::Expect.new.condition(operator, value1).data]
22
+ end
23
+ end
24
+
25
+ end]
26
+ self
27
+ end
28
+
29
+ def query
30
+ data = {
31
+ table_name: @table_name.to_s,
32
+ item: @item,
33
+ return_values: @return_values || 'ALL_OLD',
34
+ # return_consumed_capacity: @return_consumed_capacity || 'TOTAL',
35
+ # return_item_collection_metrics: @return_item_collection_metrics || 'SIZE',
36
+ }
37
+
38
+ if @expected.present?
39
+ data[:expected] = @expected
40
+ if @expected.size > 1
41
+ data[:conditional_operator] = @conditional_operator || 'AND'
42
+ end
43
+ end
44
+ data
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ module Dymos
2
+ module Query
3
+ class Query < ::Dymos::Query::Builder
4
+
5
+ def key_conditions(params)
6
+ @key = Hash[params.map do |name, expression|
7
+ operator, values = expression.split(' ', 2)
8
+ if values.nil?
9
+ [name, ::Dymos::Query::Expect.new.condition(operator, nil).data(true)]
10
+ else
11
+ value1, value2 = values.split(' ')
12
+ if value2.present?
13
+ [name, ::Dymos::Query::Expect.new.condition(operator, values).data(true)]
14
+ else
15
+ [name, ::Dymos::Query::Expect.new.condition(operator, value1).data(true)]
16
+ end
17
+ end
18
+
19
+ end]
20
+ self
21
+ end
22
+
23
+ def index_name(name)
24
+ @index_name =name
25
+ self
26
+ end
27
+
28
+ def query
29
+ {
30
+ table_name: @table_name.to_s,
31
+ index_name: @index_name.to_s,
32
+ key_conditions: @key,
33
+ consistent_read: false
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ module Dymos
2
+ module Query
3
+ class UpdateItem < ::Dymos::Query::Builder
4
+
5
+ # @param [String] name
6
+ # @return [self]
7
+ def key(name)
8
+ @key = name
9
+ self
10
+ end
11
+
12
+ # @param [Hash] param
13
+ # @param [String] action
14
+ # @return [self]
15
+ def attribute_updates(param, action="put")
16
+ @attribute_updates||={}
17
+ param.each { |key, value|
18
+ @attribute_updates[key] = {
19
+ value: value,
20
+ action: action.upcase
21
+ }
22
+ }
23
+ self
24
+ end
25
+
26
+ # @param [Hash] params
27
+ # @return [self]
28
+ def expected(params)
29
+ @expected = Hash[params.map do |name, expression|
30
+ operator, values = expression.split(' ', 2)
31
+ if values.nil?
32
+ [name, ::Dymos::Query::Expect.new.condition(operator, nil).data]
33
+ else
34
+ value1, value2 = values.split(' ')
35
+ if value2.present?
36
+ [name, ::Dymos::Query::Expect.new.condition(operator, values).data]
37
+ else
38
+ [name, ::Dymos::Query::Expect.new.condition(operator, value1).data]
39
+ end
40
+ end
41
+
42
+ end]
43
+ self
44
+ end
45
+
46
+ # @param [String] value
47
+ # @return [self]
48
+ def return_values(value)
49
+ @return_values = value.upcase
50
+ self
51
+ end
52
+
53
+ # @return [Hash]
54
+ def query
55
+ data = {
56
+ table_name: @table_name.to_s,
57
+ key: @key,
58
+ attribute_updates: @attribute_updates,
59
+ return_values: @return_values || 'ALL_NEW',
60
+ }
61
+
62
+ if @expected.present?
63
+ data[:expected] = @expected
64
+ if @expected.size > 1
65
+ data[:conditional_operator] = @conditional_operator || 'AND'
66
+ end
67
+ end
68
+ data
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module Dymos
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -66,12 +66,6 @@ describe Dymos::Model do
66
66
 
67
67
  # let(:model) { Dummy.new }
68
68
 
69
- describe Dymos do
70
- it "dymos.model" do
71
- expect(Dymos::model.name).to eq('Dymos::Model')
72
- end
73
- end
74
-
75
69
  describe :new do
76
70
 
77
71
  describe :field do
@@ -0,0 +1,15 @@
1
+ describe Dymos::Query::Attribute do
2
+ describe :new do
3
+ describe "DynamoDBClientで取り扱う値の形式に変換" do
4
+ it "文字列" do
5
+ attribute = Dymos::Query::Attribute.new('10')
6
+ expect(attribute.data).to eq(10)
7
+ end
8
+ it "数値に変換可能な文字列はintになる" do
9
+ attribute = Dymos::Query::Attribute.new('10')
10
+ expect(attribute.data).to eq(10)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,65 @@
1
+ describe Dymos::Query::Builder do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+ end
8
+
9
+ describe :list_tables do
10
+ let(:query) { Dymos::Query::Builder.new(:list_tables) }
11
+
12
+ it "command" do
13
+ expect(query.command).to eq(:list_tables)
14
+ end
15
+
16
+ it "execute" do
17
+ client = Aws::DynamoDB::Client.new
18
+ expect(query.execute(client).methods.include? :table_names).to eq(true)
19
+ end
20
+ end
21
+
22
+ describe :put_item do
23
+ before :all do
24
+ client = Aws::DynamoDB::Client.new
25
+ # client.delete_table(table_name: 'test_put_item') if client.list_tables[:table_names].include?('test_put_item')
26
+ # client.create_table(
27
+ # table_name: 'test_put_item',
28
+ # attribute_definitions: [
29
+ # {attribute_name: 'id', attribute_type: 'S'}
30
+ # ],
31
+ # key_schema: [
32
+ # {attribute_name: 'id', key_type: 'HASH'}
33
+ # ],
34
+ # provisioned_throughput: {
35
+ # read_capacity_units: 1,
36
+ # write_capacity_units: 1,
37
+ # })
38
+ # client.put_item(table_name: 'test_put_item', item: {id: 'hoge', name: '太郎'})
39
+ # client.put_item(table_name: 'test_put_item', item: {id: 'fuga', name: '次郎'})
40
+ # client.put_item(table_name: 'test_put_item', item: {id: 'piyo', name: '三郎'})
41
+ end
42
+ let(:client) { Aws::DynamoDB::Client.new }
43
+ let(:query) { Dymos::Query::Builder.new(:put_item, "test_put_item") }
44
+
45
+ it 'put' do
46
+ # User.put.item(id:"hoge",name:"太郎").expected(id:'hoge')
47
+ # query.query = {
48
+ # table_name: query.table_name,
49
+ # item: {
50
+ # id: "hoge",
51
+ # name: "太郎2",
52
+ # },
53
+ # expected: {
54
+ # id: Dymos::Query::Expect.new(:s).condition(:==, 'hoge').data,
55
+ # name: Dymos::Query::Expect.new(:s).condition(:==, '太郎2').data,
56
+ # },
57
+ # return_values: "ALL_OLD",
58
+ # }
59
+ # result = query.execute client
60
+ # expect(result).to eq('hoge')
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -0,0 +1,5 @@
1
+ describe Dymos::Query::Expect do
2
+ describe :new do
3
+ end
4
+ end
5
+
@@ -0,0 +1,55 @@
1
+ describe Dymos::Query::GetItem do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+
8
+ client = Aws::DynamoDB::Client.new
9
+ client.delete_table(table_name: 'test_get_item') if client.list_tables[:table_names].include?('test_get_item')
10
+ client.create_table(
11
+ table_name: 'test_get_item',
12
+ attribute_definitions: [
13
+ {attribute_name: 'id', attribute_type: 'S'},
14
+ {attribute_name: 'category_id', attribute_type: 'N'},
15
+ ],
16
+ key_schema: [
17
+ {attribute_name: 'id', key_type: 'HASH'},
18
+ {attribute_name: 'category_id', key_type: 'RANGE'}
19
+ ],
20
+ provisioned_throughput: {
21
+ read_capacity_units: 1,
22
+ write_capacity_units: 1,
23
+ })
24
+ client.put_item(table_name: 'test_get_item', item: {id: 'hoge', category_id: 0, name: '太郎'})
25
+ client.put_item(table_name: 'test_get_item', item: {id: 'hoge', category_id: 1})
26
+
27
+ class TestItem < Dymos::Model
28
+ table :test_get_item
29
+ field :id, :string
30
+ end
31
+ end
32
+
33
+ let(:client) { Aws::DynamoDB::Client.new }
34
+ describe :put_item do
35
+ describe "クエリ生成" do
36
+ it "追加のみ" do
37
+ query = TestItem.get.key(id: 'hoge', category_id: 1)
38
+ expect(query.query).to eq({
39
+ table_name: "test_get_item",
40
+ key: {id: "hoge", category_id: 1},
41
+ consistent_read: true,
42
+ })
43
+ # p client.scan(table_name: "test_get_item")
44
+ # res = query.execute client
45
+ # p res
46
+
47
+
48
+ # expect(res).to eq({})
49
+ # p client.scan(table_name:"test_get_item")
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,316 @@
1
+ describe Dymos::Query::PutItem do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+
8
+ client = Aws::DynamoDB::Client.new
9
+ client.delete_table(table_name: 'test_put_item') if client.list_tables[:table_names].include?('test_put_item')
10
+ client.create_table(
11
+ table_name: 'test_put_item',
12
+ attribute_definitions: [
13
+ {attribute_name: 'id', attribute_type: 'S'},
14
+ ],
15
+ key_schema: [
16
+ {attribute_name: 'id', key_type: 'HASH'}
17
+ ],
18
+ provisioned_throughput: {
19
+ read_capacity_units: 1,
20
+ write_capacity_units: 1,
21
+ })
22
+ client.put_item(table_name: 'test_put_item', item: {id: 'hoge', name: '太郎'})
23
+ client.put_item(table_name: 'test_put_item', item: {id: 'fuga', category_id: 1})
24
+
25
+ class TestItem < Dymos::Model
26
+ table :test_put_item
27
+ field :id, :string
28
+ end
29
+ end
30
+
31
+ let(:client) { Aws::DynamoDB::Client.new }
32
+ describe :put_item do
33
+ describe "クエリ生成" do
34
+ it "追加のみ" do
35
+ query = TestItem.put.item(id: "foo", name: "John")
36
+ expect(query.query).to eq({
37
+ table_name: "test_put_item",
38
+ item: {id: "foo", name: "John"},
39
+ return_values: "ALL_OLD",
40
+ })
41
+ end
42
+
43
+ end
44
+ it "条件なしput_item実行 追加時はattributesがnilになる" do
45
+ query = TestItem.put.item(id: "foo", name: "John")
46
+ result = query.execute client
47
+ expect(result).to eq(nil)
48
+ end
49
+
50
+ # it "条件ありput_item" do
51
+ # query = TestPutItem.put.item(id: "hoge", name: "次郎").expected(name: "== 太郎")
52
+ # expect(query.query).to eq({
53
+ # table_name: "test_put_item",
54
+ # item: {id: "hoge", name: "次郎"},
55
+ # :expected => {:name => {:value => "太郎", :comparison_operator => "EQ"}},
56
+ # return_values: "ALL_OLD",
57
+ # })
58
+ #
59
+ # query = TestPutItem.put.item(id: "hoge", name: "次郎").expected(category_id: "== 1")
60
+ # expect(query.query).to eq({
61
+ # table_name: "test_put_item",
62
+ # item: {id: "hoge", name: "次郎"},
63
+ # :expected => {:category_id => {:value => 1, :comparison_operator => "EQ"}},
64
+ # return_values: "ALL_OLD",
65
+ # })
66
+ #
67
+ # query = TestPutItem.put.item(id: "hoge", name: "次郎").expected(category_id: "between 0 2")
68
+ # expect(query.query).to eq({
69
+ # table_name: "test_put_item",
70
+ # item: {id: "hoge", name: "次郎"},
71
+ # :expected => {:category_id => {:attribute_value_list => [0, 2], :comparison_operator => "BETWEEN"}},
72
+ # return_values: "ALL_OLD",
73
+ # })
74
+ # end
75
+
76
+ # it "条件ありput_item実行 成功すると古いデータを返す" do
77
+ # query = TestPutItem.put.item(id: "hoge", name: "次郎").expected(name: "== 太郎")
78
+ # result = query.execute client
79
+ # expect(result.attributes).to eq({id: "hoge", name: "太郎"})
80
+ # end
81
+
82
+ describe "条件指定" do
83
+ it "undefined operator" do
84
+ expect{TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "= 1")}.to raise_error(ArgumentError)
85
+ end
86
+
87
+ describe :== do
88
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "== 1") }
89
+ it :query do
90
+ expect(query.query).to eq(table_name: "test_put_item",
91
+ item: {id: "fuga", category_id: 1},
92
+ expected: {:category_id => {:value => 1, :comparison_operator => "EQ"}},
93
+ return_values: "ALL_OLD")
94
+ end
95
+ it :execute do
96
+ result = query.execute client
97
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
98
+ end
99
+ it :error do
100
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "== 2")
101
+ expect(query.execute(client)).to eq(false)
102
+ end
103
+ end
104
+
105
+ describe :!= do
106
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "!= 0") }
107
+ it :query do
108
+ expect(query.query).to eq(table_name: "test_put_item",
109
+ item: {id: "fuga", category_id: 1},
110
+ expected: {:category_id => {:value => 0, :comparison_operator => "NE"}},
111
+ return_values: "ALL_OLD")
112
+ end
113
+ it :execute do
114
+ result = query.execute client
115
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
116
+ end
117
+ it :error do
118
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "== 2")
119
+ expect(query.execute(client)).to eq(false)
120
+ end
121
+ end
122
+
123
+ describe :> do
124
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "> 0") }
125
+ it :query do
126
+ expect(query.query).to eq(table_name: "test_put_item",
127
+ item: {id: "fuga", category_id: 1},
128
+ expected: {:category_id => {:value => 0, :comparison_operator => "GT"}},
129
+ return_values: "ALL_OLD")
130
+ end
131
+ it :execute do
132
+ result = query.execute client
133
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
134
+ end
135
+ it :error do
136
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "> 2")
137
+ expect(query.execute(client)).to eq(false)
138
+ end
139
+ end
140
+
141
+ describe :>= do
142
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: ">= 1") }
143
+ it :query do
144
+ expect(query.query).to eq(table_name: "test_put_item",
145
+ item: {id: "fuga", category_id: 1},
146
+ expected: {:category_id => {:value => 1, :comparison_operator => "GE"}},
147
+ return_values: "ALL_OLD")
148
+ end
149
+ it :execute do
150
+ result = query.execute client
151
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
152
+ end
153
+ it :error do
154
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "> 2")
155
+ expect(query.execute(client)).to eq(false)
156
+ end
157
+ end
158
+
159
+ describe :< do
160
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "< 2") }
161
+ it :query do
162
+ expect(query.query).to eq(table_name: "test_put_item",
163
+ item: {id: "fuga", category_id: 1},
164
+ expected: {:category_id => {:value => 2, :comparison_operator => "LT"}},
165
+ return_values: "ALL_OLD")
166
+ end
167
+ it :execute do
168
+ result = query.execute client
169
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
170
+ end
171
+ it :error do
172
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "< 0")
173
+ expect(query.execute(client)).to eq(false)
174
+ end
175
+ end
176
+
177
+ describe :<= do
178
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "<= 1") }
179
+ it :query do
180
+ expect(query.query).to eq(table_name: "test_put_item",
181
+ item: {id: "fuga", category_id: 1},
182
+ expected: {:category_id => {:value => 1, :comparison_operator => "LE"}},
183
+ return_values: "ALL_OLD")
184
+ end
185
+ it :execute do
186
+ result = query.execute client
187
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
188
+ end
189
+ it :error do
190
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "<= 0")
191
+ expect(query.execute(client)).to eq(false)
192
+ end
193
+ end
194
+
195
+ describe :between do
196
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "between 1 2") }
197
+ it :query do
198
+ expect(query.query).to eq(table_name: "test_put_item",
199
+ item: {id: "fuga", category_id: 1},
200
+ expected: {:category_id => {:attribute_value_list => [1, 2], :comparison_operator => "BETWEEN"}},
201
+ return_values: "ALL_OLD")
202
+ end
203
+ it :execute do
204
+ result = query.execute client
205
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
206
+ end
207
+ it :error do
208
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(category_id: "between 2 3")
209
+ expect(query.execute(client)).to eq(false)
210
+ end
211
+ end
212
+
213
+ describe :contain do
214
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(id: "contains uga") }
215
+ it :query do
216
+ expect(query.query).to eq(table_name: "test_put_item",
217
+ item: {id: "fuga", category_id: 1},
218
+ expected: {:id => {:value => "uga", :comparison_operator => "CONTAINS"}},
219
+ return_values: "ALL_OLD")
220
+ end
221
+ it :execute do
222
+ result = query.execute client
223
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
224
+ end
225
+ it :error do
226
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(id: "contains ppp")
227
+ expect(query.execute(client)).to eq(false)
228
+ end
229
+ end
230
+
231
+ describe :not_contain do
232
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(id: "not_contains ppp") }
233
+ it :query do
234
+ expect(query.query).to eq(table_name: "test_put_item",
235
+ item: {id: "fuga", category_id: 1},
236
+ expected: {:id => {:value => "ppp", :comparison_operator => "NOT_CONTAINS"}},
237
+ return_values: "ALL_OLD")
238
+ end
239
+ it :execute do
240
+ result = query.execute client
241
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
242
+ end
243
+ it :error do
244
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(id: "not_contains uga")
245
+ expect(query.execute(client)).to eq(false)
246
+ end
247
+ end
248
+
249
+ describe :begins_with do
250
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(id: "begins_with fug") }
251
+ it :query do
252
+ expect(query.query).to eq(table_name: "test_put_item",
253
+ item: {id: "fuga", category_id: 1},
254
+ expected: {:id => {:value => "fug", :comparison_operator => "BEGINS_WITH"}},
255
+ return_values: "ALL_OLD")
256
+ end
257
+ it :execute do
258
+ result = query.execute client
259
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
260
+ end
261
+ it :error do
262
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(id: "begins_with uga")
263
+ expect(query.execute(client)).to eq(false)
264
+ end
265
+ end
266
+
267
+ describe :is_null do
268
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(bar: "is_null") }
269
+ it :query do
270
+ expect(query.query).to eq(table_name: "test_put_item",
271
+ item: {id: "fuga", category_id: 1},
272
+ expected: {:bar => {:comparison_operator => "NULL"}},
273
+ return_values: "ALL_OLD")
274
+ end
275
+ it :execute do
276
+ result = query.execute client
277
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
278
+ end
279
+ it :error do
280
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(id: "is_null")
281
+ expect(query.execute(client)).to eq(false)
282
+ end
283
+ end
284
+
285
+ describe :is_not_null do
286
+ let(:query) { TestItem.put.item(id: "fuga", category_id: 1).expected(id: "is_not_null") }
287
+ it :query do
288
+ expect(query.query).to eq(table_name: "test_put_item",
289
+ item: {id: "fuga", category_id: 1},
290
+ expected: {:id => {:comparison_operator => "NOT_NULL"}},
291
+ return_values: "ALL_OLD")
292
+ end
293
+ it :execute do
294
+ result = query.execute client
295
+ expect(result.attributes).to eq({id: "fuga", category_id: 1})
296
+ end
297
+ it :error do
298
+ query = TestItem.put.item(id: "fuga", category_id: 1).expected(bar: "is_not_null")
299
+ expect(query.execute(client)).to eq(false)
300
+ end
301
+ end
302
+ end
303
+ #
304
+ # it :< do
305
+ # query = TestPutItem.put.item(id: "fuga", category_id: 1).expected(category_id: "< 2")
306
+ # result = query.execute client
307
+ # expect(result.attributes).to eq({id: "fuga", category_id: 1})
308
+ # end
309
+ # it :between do
310
+ # query = TestPutItem.put.item(id: "fuga", category_id: 1).expected(category_id: "between 1 2")
311
+ # result = query.execute client
312
+ # expect(result.attributes).to eq({id: "fuga", category_id: 1})
313
+ # end
314
+ end
315
+ end
316
+
@@ -0,0 +1,90 @@
1
+ describe Dymos::Query::Query do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+
8
+ client = Aws::DynamoDB::Client.new
9
+ client.delete_table(table_name: 'test_query_item') if client.list_tables[:table_names].include?('test_query_item')
10
+ client.create_table(
11
+ table_name: 'test_query_item',
12
+ attribute_definitions: [
13
+ {attribute_name: 'id', attribute_type: 'S'},
14
+ {attribute_name: 'category_id', attribute_type: 'N'},
15
+ {attribute_name: 'other_id', attribute_type: 'N'},
16
+ ],
17
+ key_schema: [
18
+ {attribute_name: 'id', key_type: 'HASH'},
19
+ {attribute_name: 'category_id', key_type: 'RANGE'}
20
+ ],
21
+ global_secondary_indexes: [
22
+ {
23
+ index_name: "index_other_id",
24
+ key_schema: [
25
+ {attribute_name: "id", key_type: "HASH"},
26
+ {attribute_name: "other_id", key_type: "RANGE"},
27
+ ],
28
+ projection: {
29
+ projection_type: "ALL",
30
+ },
31
+ provisioned_throughput: {
32
+ read_capacity_units: 1,
33
+ write_capacity_units: 1,
34
+ },
35
+ },
36
+ ],
37
+ provisioned_throughput: {
38
+ read_capacity_units: 1,
39
+ write_capacity_units: 1,
40
+ })
41
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 0, other_id: 1})
42
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 1, other_id: 2})
43
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 2, other_id: 3})
44
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 3, other_id: 4})
45
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 4, other_id: 5})
46
+ client.put_item(table_name: 'test_query_item', item: {id: 'hoge', category_id: 5, other_id: 6})
47
+
48
+ class TestItem < Dymos::Model
49
+ table :test_query_item
50
+ field :id, :string
51
+ end
52
+ end
53
+
54
+ let(:client) { Aws::DynamoDB::Client.new }
55
+ describe :put_item do
56
+ describe "クエリ生成" do
57
+ describe "グローバルセカンダリインデックスを利用した検索" do
58
+ let(:query) {
59
+ TestItem.query.index_name(:index_other_id).key_conditions(
60
+ id: "== hoge",
61
+ other_id: "between 1 3"
62
+ )
63
+ }
64
+ it :query do
65
+ expect(query.query).to eq(
66
+ table_name: 'test_query_item',
67
+ index_name: 'index_other_id',
68
+ key_conditions: {
69
+ id: {
70
+ attribute_value_list: ["hoge"],
71
+ comparison_operator: "EQ"
72
+ },
73
+ other_id: {
74
+ attribute_value_list: [1, 3],
75
+ comparison_operator: "BETWEEN"
76
+ }},
77
+ consistent_read: false,
78
+ )
79
+ end
80
+ it :execute do
81
+ res = query.execute client
82
+ expect(res.size).to eq(3)
83
+ end
84
+
85
+
86
+ end
87
+ end
88
+ end
89
+ end
90
+
@@ -0,0 +1,119 @@
1
+ describe Dymos::Query::UpdateItem do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+
8
+ client = Aws::DynamoDB::Client.new
9
+ client.delete_table(table_name: 'test_update_item') if client.list_tables[:table_names].include?('test_update_item')
10
+ client.create_table(
11
+ table_name: 'test_update_item',
12
+ attribute_definitions: [
13
+ {attribute_name: 'id', attribute_type: 'S'},
14
+ ],
15
+ key_schema: [
16
+ {attribute_name: 'id', key_type: 'HASH'}
17
+ ],
18
+ provisioned_throughput: {
19
+ read_capacity_units: 1,
20
+ write_capacity_units: 1,
21
+ })
22
+ client.put_item(table_name: 'test_update_item', item: {id: 'hoge', name: '太郎'})
23
+ client.put_item(table_name: 'test_update_item', item: {id: 'fuga', count: 0})
24
+ client.put_item(table_name: 'test_update_item', item: {id: 'poyo', name: '可奈'})
25
+ client.put_item(table_name: 'test_update_item', item: {id: 'piyo', name: '杏奈', count: 10})
26
+
27
+ class TestItem < Dymos::Model
28
+ table :test_update_item
29
+ field :id, :string
30
+ field :name, :string
31
+ field :count, :string
32
+ end
33
+ end
34
+
35
+ let(:client) { Aws::DynamoDB::Client.new }
36
+ describe :put_item do
37
+ describe "クエリ生成" do
38
+ describe "新しいアイテムを追加" do
39
+ let(:query) { TestItem.update.key(id: "foo").attribute_updates({name: "Sam"}, 'PUT') }
40
+ it :query do
41
+ expect(query.query).to eq({
42
+ table_name: "test_update_item",
43
+ key: {id: "foo"},
44
+ attribute_updates: {name: {value: "Sam", action: "PUT"}},
45
+ return_values: "ALL_NEW",
46
+ })
47
+ end
48
+ it "成功すると新しいアイテムを返す" do
49
+ res = query.execute client
50
+ expect(res.id).to eq("foo")
51
+ end
52
+ end
53
+
54
+ describe "既存のアイテムを更新" do
55
+ describe "return_values :ALL_OLD" do
56
+ it "更新した場合は更新前のアイテムを返す" do
57
+ query = TestItem.update.key(id: "hoge").attribute_updates({name: "次郎"}, 'PUT').return_values(:ALL_OLD)
58
+ res = query.execute client
59
+ expect(res.name).to eq("太郎")
60
+ end
61
+ it "追加した場合はnilを返す" do
62
+ query = TestItem.update.key(id: "bar").attribute_updates({name: "Sam"}, 'PUT').return_values(:ALL_OLD)
63
+ res = query.execute client
64
+ expect(res).to eq(nil)
65
+ end
66
+ end
67
+ it "アイテムに加算する" do
68
+ query = TestItem.update.key(id: "fuga").attribute_updates({count: 1}, 'add')
69
+ res = query.execute client
70
+ expect(res.count).to eq(1)
71
+ end
72
+
73
+ describe "条件付き更新" do
74
+ describe :== do
75
+ let(:query) { TestItem.update.key(id: "poyo").attribute_updates({name: "志保"}, 'PUT').expected(name: "== 可奈") }
76
+
77
+ it :query do
78
+ expect(query.query).to eq({
79
+ table_name: "test_update_item",
80
+ key: {id: "poyo"},
81
+ attribute_updates: {name: {value: "志保", action: "PUT"}},
82
+ expected: ({name: {value: "可奈", comparison_operator: "EQ"}}),
83
+ return_values: "ALL_NEW",
84
+ })
85
+ end
86
+ it "成功すると新しいアイテムを返す" do
87
+ res = query.execute client
88
+ expect(res.name).to eq("志保")
89
+ end
90
+ end
91
+ describe "複数条件" do
92
+ let(:query) { TestItem.update.key(id: "piyo").attribute_updates({name: "百合子"}, 'PUT')
93
+ .expected(count: "between 9 12", name: "== 杏奈", hoge:"is_null")}
94
+
95
+ it :query do
96
+ expect(query.query).to eq({
97
+ table_name: "test_update_item",
98
+ key: {id: "piyo"},
99
+ attribute_updates: {name: {value: "百合子", action: "PUT"}},
100
+ expected: ({
101
+ name: {value: "杏奈", comparison_operator: "EQ"},
102
+ count: {attribute_value_list: [9, 12], comparison_operator: "BETWEEN"},
103
+ hoge: {comparison_operator: "NULL"},
104
+ }),
105
+ return_values: "ALL_NEW",
106
+ conditional_operator: "AND"
107
+ })
108
+ end
109
+ it "成功すると新しいアイテムを返す" do
110
+ res = query.execute client
111
+ expect(res.name).to eq("百合子")
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
@@ -1,81 +1,7 @@
1
1
  require 'coveralls'
2
2
  Coveralls.wear!
3
- # This file was generated by the `rspec --init` command. Conventionally, all
4
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
- # The generated `.rspec` file contains `--require spec_helper` which will cause this
6
- # file to always be loaded, without a need to explicitly require it in any files.
7
- #
8
- # Given that it is always loaded, you are encouraged to keep this file as
9
- # light-weight as possible. Requiring heavyweight dependencies from this file
10
- # will add to the boot time of your test suite on EVERY test run, even for an
11
- # individual file that may not need all of that loaded. Instead, make a
12
- # separate helper file that requires this one and then use it only in the specs
13
- # that actually need it.
14
- #
15
- # The `.rspec` file also contains a few flags that are not defaults but that
16
- # users commonly want.
17
- #
18
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
- Dir[File.join(File.dirname(__FILE__), "..", "lib", "**/*.rb")].each{|f| require f }
20
3
 
4
+ require 'rubygems'
5
+ require 'bundler/setup'
21
6
 
22
- RSpec.configure do |config|
23
- # The settings below are suggested to provide a good initial experience
24
- # with RSpec, but feel free to customize to your heart's content.
25
- # These two settings work together to allow you to limit a spec run
26
- # to individual examples or groups you care about by tagging them with
27
- # `:focus` metadata. When nothing is tagged with `:focus`, all examples
28
- # get run.
29
- config.filter_run :focus
30
- config.run_all_when_everything_filtered = true
31
-
32
- # Many RSpec users commonly either run the entire suite or an individual
33
- # file, and it's useful to allow more verbose output when running an
34
- # individual spec file.
35
- if config.files_to_run.one?
36
- # Use the documentation formatter for detailed output,
37
- # unless a formatter has already been configured
38
- # (e.g. via a command-line flag).
39
- config.default_formatter = 'doc'
40
- end
41
-
42
- # Print the 10 slowest examples and example groups at the
43
- # end of the spec run, to help surface which specs are running
44
- # particularly slow.
45
- config.profile_examples = 10
46
-
47
- # Run specs in random order to surface order dependencies. If you find an
48
- # order dependency and want to debug it, you can fix the order by providing
49
- # the seed, which is printed after each run.
50
- # --seed 1234
51
- config.order = :random
52
-
53
- # Seed global randomization in this process using the `--seed` CLI option.
54
- # Setting this allows you to use `--seed` to deterministically reproduce
55
- # test failures related to randomization by passing the same `--seed` value
56
- # as the one that triggered the failure.
57
- Kernel.srand config.seed
58
-
59
- # rspec-expectations config goes here. You can use an alternate
60
- # assertion/expectation library such as wrong or the stdlib/minitest
61
- # assertions if you prefer.
62
- config.expect_with :rspec do |expectations|
63
- # Enable only the newer, non-monkey-patching expect syntax.
64
- # For more details, see:
65
- # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
66
- expectations.syntax = :expect
67
- end
68
-
69
- # rspec-mocks config goes here. You can use an alternate test double
70
- # library (such as bogus or mocha) by changing the `mock_with` option here.
71
- config.mock_with :rspec do |mocks|
72
- # Enable only the newer, non-monkey-patching expect syntax.
73
- # For more details, see:
74
- # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
75
- mocks.syntax = :expect
76
-
77
- # Prevents you from mocking or stubbing a method that does not exist on
78
- # a real object. This is generally recommended.
79
- mocks.verify_partial_doubles = true
80
- end
81
- end
7
+ require 'dymos'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dymos
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
  - hoshina85
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-21 00:00:00.000000000 Z
11
+ date: 2014-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -125,9 +125,24 @@ files:
125
125
  - dymos.gemspec
126
126
  - lib/dymos.rb
127
127
  - lib/dymos/attribute.rb
128
+ - lib/dymos/command.rb
128
129
  - lib/dymos/model.rb
130
+ - lib/dymos/query/attribute.rb
131
+ - lib/dymos/query/builder.rb
132
+ - lib/dymos/query/expect.rb
133
+ - lib/dymos/query/get_item.rb
134
+ - lib/dymos/query/put_item.rb
135
+ - lib/dymos/query/query.rb
136
+ - lib/dymos/query/update_item.rb
129
137
  - lib/dymos/version.rb
130
138
  - spec/lib/dymos/model_spec.rb
139
+ - spec/lib/dymos/query/attribute_spec.rb
140
+ - spec/lib/dymos/query/builder_spec.rb
141
+ - spec/lib/dymos/query/expect_spec.rb
142
+ - spec/lib/dymos/query/get_item_spec.rb
143
+ - spec/lib/dymos/query/put_item_spec.rb
144
+ - spec/lib/dymos/query/query_spec.rb
145
+ - spec/lib/dymos/query/update_item_spec.rb
131
146
  - spec/spec_helper.rb
132
147
  homepage: ''
133
148
  licenses:
@@ -155,4 +170,11 @@ specification_version: 4
155
170
  summary: aws-sdk-core-ruby dynamodb client wrapper
156
171
  test_files:
157
172
  - spec/lib/dymos/model_spec.rb
173
+ - spec/lib/dymos/query/attribute_spec.rb
174
+ - spec/lib/dymos/query/builder_spec.rb
175
+ - spec/lib/dymos/query/expect_spec.rb
176
+ - spec/lib/dymos/query/get_item_spec.rb
177
+ - spec/lib/dymos/query/put_item_spec.rb
178
+ - spec/lib/dymos/query/query_spec.rb
179
+ - spec/lib/dymos/query/update_item_spec.rb
158
180
  - spec/spec_helper.rb