sire 0.0.1 → 0.1.0

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: 30413fa266e06f4f15e3880b75de8ecf4f290948
4
- data.tar.gz: 8411be1b81784b065a1175ed80b1ad6f49794253
3
+ metadata.gz: 644bb4b56a4f703358b995a7436bc5b8bb9e85d1
4
+ data.tar.gz: ac1711f86c4d933e1a041f81ce51664be8725631
5
5
  SHA512:
6
- metadata.gz: 5e73d3110599e928b4ef46f91dbb9d3132d51d4ce2552d921eeb719607421b373b0c3d01afefa39b40275f54f8cab965eac1c38104d0367ccb7b714dedcbceb2
7
- data.tar.gz: 1d4696476497073ec0989891d730b1c7c557427dc0ce24cdba2d62a407c9c5d9110be91058e58fdf74617f42dcc8d84eb60cb83ddd9c6633f34fce32b5c3ec52
6
+ metadata.gz: 7dd2374ca218b06db34b535e59cb6da00a748fcdedb248f9aef002a93db39fd374358b1bb1ffba0381c69f8ddd0f05eb371b85a47d6d88078535a58de01bf353
7
+ data.tar.gz: ecbb9e95ba320112a4b479dc7905293a4fd99ce3dd2680e35de38fd50e3847701acb70be8110f74603dd68576139e0a91fd632ff564c5e1518c3c5278b545447
data/README.md CHANGED
@@ -1,70 +1,59 @@
1
- # Sire
2
-
3
- [![Build Status](https://travis-ci.org/mushishi78/sire.svg?branch=master)](https://travis-ci.org/mushishi78/sire)
4
- [![Gem Version](https://badge.fury.io/rb/sire.svg)](http://badge.fury.io/rb/sire)
5
-
6
- Simple Immutable Relational Entity
7
-
8
- ## Usage
9
-
10
- Sire entities can be defined like Ruby Structs:
11
-
12
- ``` ruby
13
- require 'sire'
14
-
15
- Foo = Sire.define(:name, :height)
16
- ```
17
-
18
- They are initialized with a hash, they have getters for each attribute and they include `enumerable`:
19
-
20
- ``` ruby
21
- foo_instance = Foo.new(name: 'Greg')
22
- foo_instance.name # 'Greg'
23
- foo_instance.each { |k, v| puts "My #{k} is #{v.inspect}" }
24
- # $ "My name is Greg"
25
- # $ "My height is nil"
26
- ```
27
-
28
- They can be used to define new entities that inherit their getters:
29
-
30
- ``` ruby
31
- Bar = Foo.define(:stick)
32
- bar_instance = Bar.new(name: 'Harry', stick: 13)
33
- ```
34
-
35
- ### Relations
36
-
37
- If an attribute matches the name of an Entity, it is treated as a relation and can be defined with nested hashes:
38
-
39
- ``` ruby
40
- Product = Sire.define(:name, :price)
41
- LineItem = Sire.define(:product, :quantity)
42
-
43
- line_item_instance = LineItem.new(product: {name: 'Cart', price: 1.30},
44
- quantity: 2)
45
- ```
46
-
47
- If an attribute is the plural of the name of an Entity, it is treated as a has_many relation and can be defined with an array of nested hashes:
48
-
49
- ``` ruby
50
- Inventory = Sire.define(:products)
51
-
52
- inventory_instance = Inventory.new(products: [{ name: 'bag', price: 0.66 },
53
- { name: 'shoes', price: 1.30 }])
54
- ```
55
-
56
- ## Installation
57
-
58
- Add this line to your application's Gemfile:
59
-
60
- ```ruby
61
- gem 'sire'
62
- ```
63
-
64
- ## Contributing
65
-
66
- 1. Fork it ( https://github.com/[my-github-username]/sire/fork )
67
- 2. Create your feature branch (`git checkout -b my-new-feature`)
68
- 3. Commit your changes (`git commit -am 'Add some feature'`)
69
- 4. Push to the branch (`git push origin my-new-feature`)
70
- 5. Create a new Pull Request
1
+ # Sire
2
+
3
+ [![Build Status](https://travis-ci.org/mushishi78/sire.svg?branch=master)](https://travis-ci.org/mushishi78/sire)
4
+ [![Gem Version](https://badge.fury.io/rb/sire.svg)](http://badge.fury.io/rb/sire)
5
+
6
+ Simple Immutable Relational Entity
7
+
8
+ ## Defining
9
+
10
+ ``` ruby
11
+ require 'sire'
12
+
13
+ Order = Sire.entity(:line_items, :total)
14
+ LineItem = Sire.entity(:purchasable, :quantity)
15
+ LineItems = Sire.aggregate(LineItem)
16
+ Product = Sire.entity(:name, :price)
17
+ Service = Sire.entity(:name, :rate)
18
+ Purchasable = Sire.relation(Product, Service)
19
+ Rate = Sire.entity(:price, :period)
20
+ ```
21
+
22
+ ## Initializing
23
+
24
+ ``` ruby
25
+ rate = Rate[price: 13.99, period: :weekly]
26
+ service1 = Service[name: 'Consulting', rate: rate]
27
+ service2 = Service[name: 'Support', rate: { price: 56.50, period: :monthly }]
28
+ # Service[name: "Support", rate: Rate[price: 56.5, period: :monthly]]
29
+ line_item = LineItem[purchasable: { name: 'Hat', price: 5.36 }, quantity: 2]
30
+ # LineItem[purchasable: Product[name: "Hat", price: 5.36], quantity: 2]
31
+ order = Order[line_items: [line_item, {purchasable: service1, quantity: 1}], total: 12.50]
32
+ # Order[line_items: [LineItem[purchasable: Product[name: "Hat", price: 5.36], quantity: 2], LineItem[purchasable: Service[name: "Consulting", rate: Rate[price: 13.99, period: :weekly]], quantity: 1]], total: 12.5]
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ``` ruby
38
+ rate.price # 13.99
39
+ rate.attributes # [:price, :period]
40
+ rate2 = rate.merge(price: 10.00) # Rate[price: 10.00, period: :weekly]
41
+ rate == rate2 # false
42
+ rate.map { |k, v| "#{k} = #{v}" }.join(', ') # "price = 13.99, period = weekly"
43
+ ```
44
+
45
+ ## Installation
46
+
47
+ Add this line to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'sire'
51
+ ```
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it ( https://github.com/[my-github-username]/sire/fork )
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create a new Pull Request
data/lib/sire.rb CHANGED
@@ -1,9 +1,19 @@
1
1
  require 'sire/entity'
2
+ require 'sire/relation'
3
+ require 'sire/aggregate'
2
4
 
3
5
  module Sire
4
6
  class << self
5
- def define(*attributes, &b)
6
- Entity.define(*attributes, &b)
7
+ def entity(*attributes)
8
+ Entity.define(*attributes)
9
+ end
10
+
11
+ def relation(*entity_types)
12
+ Relation.define(*entity_types)
13
+ end
14
+
15
+ def aggregate(*entity_types)
16
+ Aggregate.define(*entity_types)
7
17
  end
8
18
  end
9
19
  end
@@ -0,0 +1,16 @@
1
+ require 'sire/relation'
2
+
3
+ module Sire
4
+ class Aggregate < Relation
5
+ def initialize(*entity_types)
6
+ super
7
+ extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def new(array)
12
+ array.map { |v| super(v) }.freeze
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/sire/entity.rb CHANGED
@@ -1,25 +1,21 @@
1
1
  require 'sire/struct'
2
- require 'descendants_tracker'
2
+ require 'sire/entity_builder'
3
+ require 'sire/aggregate'
3
4
 
4
5
  module Sire
5
6
  class Entity < Struct
6
- extend DescendantsTracker
7
-
8
- def merge(other)
9
- hash, other_hash = to_h, other.to_h
10
-
11
- other_hash.each do |k, other_value|
12
- value = hash[k]
13
- hash[k] = value.is_a?(Array) ? value + other_value : other_value
7
+ class << self
8
+ def inherited(child)
9
+ EntityBuilder.nestables << child
14
10
  end
15
11
 
16
- self.class.new(hash)
12
+ def new(arg = {})
13
+ arg.is_a?(self) ? arg : super
14
+ end
17
15
  end
18
16
 
19
- private
20
-
21
- def nestable_classes
22
- Entity.descendants
17
+ def initialize(hash)
18
+ super(EntityBuilder.build(hash))
23
19
  end
24
20
  end
25
21
  end
@@ -0,0 +1,54 @@
1
+ require 'inflecto'
2
+
3
+ module Sire
4
+ module EntityBuilder
5
+ class << self
6
+ def build(hash)
7
+ create_lookup unless lookup_up_to_date?
8
+ parse_values(hash)
9
+ end
10
+
11
+ def nestables
12
+ @nestables ||= []
13
+ end
14
+
15
+ def primitives
16
+ @primitves ||= [String, Numeric, Rational, Complex, Integer, Fixnum, Float, Bignum,
17
+ TrueClass, FalseClass, Symbol, Time, Regexp, NilClass]
18
+ end
19
+
20
+ private
21
+
22
+ def create_lookup
23
+ @nestable_lookup = Hash[nestables.map { |n| [to_sym(n), n] }]
24
+ end
25
+
26
+ def lookup_up_to_date?
27
+ @nestable_lookup.respond_to?(:values) && @nestable_lookup.values == nestables
28
+ end
29
+
30
+ def parse_values(hash)
31
+ Hash[hash.map { |k, v| nested(k, v) || primitive(k, v) || invalid(k) }]
32
+ end
33
+
34
+ def nested(k, v)
35
+ return unless nested_class = @nestable_lookup[k]
36
+ [k, nested_class.new(v)]
37
+ end
38
+
39
+ def primitive(k, v)
40
+ [k, v] if primitives.include?(v.class)
41
+ end
42
+
43
+ def invalid(k)
44
+ fail InvalidValueError, "#{k} is invalid. Accepted types: #{primitives | nestables}"
45
+ end
46
+
47
+ InvalidValueError = Class.new(StandardError)
48
+
49
+ def to_sym(klass)
50
+ Inflecto.underscore(Inflecto.demodulize(klass.to_s)).to_sym
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ module Sire
2
+ class ImmutableDelegator < BasicObject
3
+ def initialize(obj)
4
+ @obj = obj
5
+ end
6
+
7
+ def method_missing(*args, &b)
8
+ return super unless obj.respond_to?(args.first)
9
+ res = obj.send(*args, &b)
10
+ res.class == obj.class ? create_new(res) : res
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :obj
16
+
17
+ def create_new(res)
18
+ self.class.new(res)
19
+ end
20
+
21
+ def respond_to_missing?(meth, privates = false)
22
+ obj.respond_to?(meth, privates) || super
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ require 'sire/entity_builder'
2
+
3
+ module Sire
4
+ class Relation < Module
5
+ class << self
6
+ alias_method :define, :new
7
+ end
8
+
9
+ def initialize(*entity_types)
10
+ @entity_types = entity_types
11
+ extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+ attr_reader :entity_types
16
+
17
+ def self.extended(child)
18
+ EntityBuilder.nestables << child
19
+ end
20
+
21
+ # TODO: Should iterate over entity_types and find the best key match
22
+ def new(value)
23
+ acceptable?(value) ? value : default.new(value)
24
+ end
25
+
26
+ private
27
+
28
+ def acceptable?(value)
29
+ entity_types.include?(value.class)
30
+ end
31
+
32
+ def default
33
+ entity_types[0]
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/sire/struct.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'sire/value_parser'
2
-
3
1
  module Sire
4
2
  class Struct
5
3
  include Enumerable
@@ -7,12 +5,11 @@ module Sire
7
5
  class << self
8
6
  attr_reader :attributes
9
7
 
10
- def define(*attributes, &b)
8
+ def define(*attributes)
11
9
  Class.new(self) do
12
10
  attr_accessor(*attributes)
13
11
  private(*attributes.map { |a| "#{a}=" })
14
12
  @attributes = [*attributes].freeze
15
- class_eval(&b) if b
16
13
  end
17
14
  end
18
15
 
@@ -22,13 +19,15 @@ module Sire
22
19
  end
23
20
 
24
21
  def initialize(hash = {})
25
- hash.each { |k, v| send("#{k}=", parser.parse_value(k, v)) }
22
+ hash.each { |k, v| send("#{k}=", v) }
26
23
  freeze
27
24
  end
28
25
 
29
26
  def merge(other)
30
- self.class.new(to_h.merge(other.to_h))
27
+ other_hash = other.respond_to?(:to_h) ? other.to_h : other
28
+ self.class.new(to_h.merge(other_hash))
31
29
  end
30
+ alias_method :update, :merge
32
31
 
33
32
  def attributes
34
33
  self.class.attributes
@@ -50,14 +49,8 @@ module Sire
50
49
  to_s
51
50
  end
52
51
 
53
- private
54
-
55
- def parser
56
- @parser ||= ValueParser.new(nestable_classes)
57
- end
58
-
59
- def nestable_classes
60
- []
52
+ def to_h
53
+ Hash[to_a]
61
54
  end
62
55
  end
63
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-22 00:00:00.000000000 Z
11
+ date: 2015-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inflecto
@@ -79,10 +79,12 @@ files:
79
79
  - LICENSE.txt
80
80
  - README.md
81
81
  - lib/sire.rb
82
- - lib/sire/delegator.rb
82
+ - lib/sire/aggregate.rb
83
83
  - lib/sire/entity.rb
84
+ - lib/sire/entity_builder.rb
85
+ - lib/sire/immutable_delegator.rb
86
+ - lib/sire/relation.rb
84
87
  - lib/sire/struct.rb
85
- - lib/sire/value_parser.rb
86
88
  homepage:
87
89
  licenses:
88
90
  - MIT
@@ -1,16 +0,0 @@
1
- require 'delegate'
2
-
3
- module Sire
4
- class Delegator < SimpleDelegator
5
- def method_missing(*args, &b)
6
- result = super
7
- result.class == __getobj__.class ? create_new(result) : result
8
- end
9
-
10
- private
11
-
12
- def create_new(result)
13
- self.class.new(result)
14
- end
15
- end
16
- end
@@ -1,57 +0,0 @@
1
- require 'inflecto'
2
-
3
- module Sire
4
- class ValueParser
5
- def initialize(nestable_classes = [])
6
- @nestable_classes = nestable_classes.reduce({}) do |h, c|
7
- h.merge(demodulize_and_underscore(c) => c)
8
- end
9
- end
10
-
11
- def parse_value(key, value)
12
- @key, @value = key, value
13
- return value.freeze if primitive?
14
- return create_nested if single_nested_class
15
- return create_many_nested if plural_nested_class
16
- invalid
17
- end
18
-
19
- private
20
-
21
- def primitive?
22
- primitives.include?(@value.class)
23
- end
24
-
25
- def primitives
26
- @@primitives ||= [String, Numeric, Rational, Complex, Integer, Fixnum, Float, Bignum,
27
- TrueClass, FalseClass, Symbol, Date, Time, Regexp, NilClass]
28
- end
29
-
30
- def create_nested(nested_class = single_nested_class, arg = @value)
31
- arg.is_a?(nested_class) ? arg : nested_class.new(arg).freeze
32
- end
33
-
34
- def create_many_nested
35
- [*@value.map { |arg| create_nested(plural_nested_class, arg) }].freeze
36
- end
37
-
38
- def single_nested_class
39
- @single_nested_class ||= @nestable_classes[@key.to_s]
40
- end
41
-
42
- def plural_nested_class
43
- @plural_nested_class ||= @nestable_classes[Inflecto.singularize(@key.to_s)]
44
- end
45
-
46
- def invalid
47
- message = "#{@key} is invalid. Accepted types: #{primitives | @nestable_classes.values}"
48
- fail InvalidValueError, message
49
- end
50
-
51
- def demodulize_and_underscore(word)
52
- Inflecto.underscore(Inflecto.demodulize(word))
53
- end
54
-
55
- InvalidValueError = Class.new(StandardError)
56
- end
57
- end