lazy_record 0.1.1 → 0.1.2

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: 2a600da5b0f7578c40fca42e349acfdd8ead86f6
4
- data.tar.gz: 39fdf3dd67b33c91235b7d18a067a55ecac89370
3
+ metadata.gz: 6f5ad82781d6334b11179ab0981b622e4d47494a
4
+ data.tar.gz: d82b3d440be2c90ff4354d557dc3f1ac8fa53ab5
5
5
  SHA512:
6
- metadata.gz: cb2bbded1a40899dcdeedb83369810d4a7724fcd1b8debe0ace25549d5afb1a5927f5fcbe8c85877bfa17373afcf96db3cb144f036ec35032db677155a13d0d8
7
- data.tar.gz: 02b008aa58f14d93b5d5e0f4e28c064826f795b6e47403918bff4f62a7bf827440de54a4035e9e4ec2a0fda3bc9aa6674576c1e2a7d2a8ac9a01336defd431ca
6
+ metadata.gz: 419b37034fd69cc6c115ede5ee8b663f589bd432ea7f25f6942378d7ced2b319e53eabf4ab056a9b063792804a6d5929934a3e23b448246302df275e6b68726b
7
+ data.tar.gz: 5750864f0f361b69d54e3c063b2c8b48444d903c933c957224592fb8261d363bc12f1ec175ec3fab87850d6312db525ba82bbe000907785db3ff8f8dec317785
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # LazyRecord
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lazy_record`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ LazyRecord writes a bunch of boilerplate code for your POROs, similarly to what you'd expect ActiveRecord to do for your database-backed objects. This project is an attempt to understand and explore dynamic programming techniques in Ruby, and demystify some of the Rails magic. Maybe someone will find it useful.
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,20 +20,149 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ All objects that inherit from `LazyRecord::Base` get block syntax added to their `#initialize` method.
24
+
25
+ ```ruby
26
+ class Thing < LazyRecord::Base
27
+ end
28
+
29
+ thing = Thing.new do |t|
30
+ t.inspect
31
+ end
32
+ # => #<Thing id: 1>
33
+ ```
34
+ Every LazyRecord object is assigned an auto-incrementing ID after initialization. IDs reset when the program is terminated.
35
+
36
+ Use `lr_attr_accessor` like you would use `attr_accessor`. You'll get hash syntax in your `#intialize` method for attribute setting.
37
+
38
+ ```ruby
39
+ class Thing < LazyRecord::Base
40
+ lr_attr_accessor :stuff, :junk
41
+ end
42
+
43
+ thing = Thing.new stuff: 'stuff' do |t|
44
+ t.junk = 'junk'
45
+ end
46
+ # => #<Thing id: 1, stuff: "stuff", junk: "junk">
47
+ ```
48
+
49
+ Validate presence of attributes with `lr_validates` like you would with ActiveRecord. Failed validations will return false and the ID will not be incremented. More validation options coming in the future.
50
+
51
+ ```ruby
52
+ class Thing < LazyRecord::Base
53
+ lr_attr_accessor :stuff, :junk
54
+ lr_validates :stuff, presence: true
55
+ end
56
+
57
+ thing = Thing.new junk: 'junk'
58
+ ArgumentError
59
+ stuff must be given
60
+ #<Thing id: nil, stuff: nil, junk: "junk">
61
+ # => false
62
+ ```
63
+ Use `lr_has_many` to set up associated collections of another class. `lr_belongs_to` will be added in a future update.
64
+
65
+ ```ruby
66
+ class Whatever < LazyRecord::Base
67
+ end
68
+
69
+ class Thing < LazyRecord::Base
70
+ lr_attr_accessor :stuff, :junk
71
+ lr_validates :stuff, presence: true
72
+ lr_has_many :whatevers
73
+ end
74
+
75
+ whatever = Whatever.new
76
+ # => #<Whatever id: 1>
77
+
78
+ thing = Thing.new do |t|
79
+ t.stuff = 'stuff'
80
+ t.whatevers << whatever
81
+ end
82
+ # => #<Thing id: 1, stuff: "stuff", junk: nil>
83
+
84
+ thing.whatevers
85
+ # => #<WhateverRelation [#<Whatever id: 1>]>
86
+ ```
87
+
88
+ Use `lr_scope` and `#where` to create class scope methods and query objects. Works just like ActiveRecord scopes, including scope chaining. Only since it is all Ruby and no SQL, use `==` as the comparison operator.
89
+
90
+ ```ruby
91
+ class Whatever < LazyRecord::Base
92
+ lr_attr_accessor :party_value, :sleepy_value
93
+ lr_scope :big_party, -> { where('party_value > 10') }
94
+ lr_scope :low_sleepy, -> { where('sleepy_value < 10') }
95
+ end
96
+
97
+ class Thing < LazyRecord::Base
98
+ lr_attr_accessor :stuff, :junk
99
+ lr_validates :stuff, presence: true
100
+ lr_has_many :whatevers
101
+ end
102
+
103
+ Whatever.new party_value: 12, sleepy_value: 12
104
+ Whatever.new party_value: 13, sleepy_value: 3
105
+ Whatever.new party_value: 4, sleepy_value: 11
106
+ Whatever.new party_value: 3, sleepy_value: 5
107
+ thing = Thing.new do |t|
108
+ t.stuff = 'stuff'
109
+ t.whatevers = Whatever.all
110
+ end
111
+ # => #<Thing id: 1, stuff: "stuff", junk: nil>
112
+
113
+ thing.whatevers.big_party
114
+ # => #<WhateverRelation [#<Whatever id: 1, party_value: 12, sleepy_value: 12>, #<Whatever id: 2, party_value: 13, sleepy_value: 3>]>
115
+
116
+ thing.whatevers.low_sleepy
117
+ # => #<WhateverRelation [#<Whatever id: 2, party_value: 13, sleepy_value: 3>, #<Whatever id: 4, party_value: 3, sleepy_value: 5>]>
118
+
119
+ thing.whatevers.big_party.low_sleepy
120
+ # => #<WhateverRelation [#<Whatever id: 2, party_value: 13, sleepy_value: 3>]>
121
+
122
+ Whatever.low_sleepy
123
+ # => #<WhateverRelation [#<Whatever id: 2, party_value: 13, sleepy_value: 3>, #<Whatever id: 4, party_value: 3, sleepy_value: 5>]>
124
+ ```
125
+
126
+ Use `lr_method` for an alternative API for defining short instance methods. Can use lambda syntax or string syntax. Only good for quick one-liners. If the method references `self` of the instance, either explicitly or implicitly, it needs to use the string syntax, since any variables not passed into the lambda will be evaluated in the context of the Class level scope.
127
+
128
+ ```ruby
129
+ class Whatever < LazyRecord::Base
130
+ lr_attr_accessor :party_value, :sleepy_value, :right
131
+ lr_scope :big_party, -> { where('party_value > 10') }
132
+ lr_scope :low_sleepy, -> { where('sleepy_value < 10') }
133
+ end
134
+
135
+ class Thing < LazyRecord::Base
136
+ lr_attr_accessor :stuff, :junk
137
+ lr_validates :stuff, presence: true
138
+ lr_has_many :whatevers
139
+ lr_method :speak, -> (string) { puts string }
140
+ lr_method :add_whatever, 'hmm', 'whatevers << Whatever.new(right: hmm)'
141
+ end
142
+
143
+ thing = Thing.new stuff: 'stuff'
144
+ thing.speak "I'm a thing"
145
+ # I'm a thing
146
+ # => nil
147
+
148
+ thing.add_whatever(true)
149
+ # => [#<Whatever id: 1, party_value: nil, sleepy_value: nil, right: true>]
150
+
151
+ thing.whatevers
152
+ # => #<WhateverRelation [#<Whatever id: 1, party_value: nil, sleepy_value: nil, right: true>]>
153
+ ```
26
154
 
27
155
  ## Development
28
156
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
157
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. There is an `example` directory with some LazyRecord classes defined.
30
158
 
31
159
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
160
 
33
161
  ## Contributing
34
162
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/lazy_record.
163
+ Bug reports and pull requests are welcome on GitHub at https://github.com/msimonborg/lazy_record.
36
164
 
37
165
 
38
166
  ## License
39
167
 
40
168
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
-
@@ -2,14 +2,15 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'bundler/setup'
5
- require 'lazy_record'
5
+ require_relative '../config/environment'
6
+
6
7
 
7
8
  # You can add fixtures and/or initialization code here to make experimenting
8
9
  # with your gem easier. You can also use a different console, if you like.
9
10
 
10
11
  # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
12
+ require "pry"
13
+ Pry.start
13
14
 
14
- require 'irb'
15
- IRB.start(__FILE__)
15
+ # require 'irb'
16
+ # IRB.start(__FILE__)
@@ -0,0 +1,10 @@
1
+ example = File.expand_path('../../example', __FILE__)
2
+ $LOAD_PATH.unshift(example) unless $LOAD_PATH.include?(example)
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'lazy_record'
7
+ autoload :Cat, 'cat'
8
+ autoload :Dog, 'dog'
9
+ autoload :Friend, 'friend'
10
+ autoload :Person, 'person'
@@ -0,0 +1,3 @@
1
+ class Cat < LazyRecord::Base
2
+ lr_attr_accessor :name, :breed, :weight, :favorite_food
3
+ end
@@ -0,0 +1,10 @@
1
+ class Dog < LazyRecord::Base
2
+ lr_attr_accessor :name, :breed, :weight
3
+ lr_has_many :friends
4
+
5
+ def initialize(opts = {}, &block)
6
+ super
7
+ self.friends << Friend.new(opts) if opts[:friend] == true
8
+ self.name = name + 'y' if opts[:cute] == true
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class Friend < LazyRecord::Base
2
+ lr_attr_accessor :years_known
3
+ end
@@ -0,0 +1,78 @@
1
+ class Person < LazyRecord::Base
2
+ lr_attr_accessor :name, :age, :haircut
3
+ lr_has_many :dogs, :cats
4
+ lr_accepts_nested_attributes_for :dogs, :cats
5
+
6
+ can_multiply
7
+
8
+ lr_scope :new_with_dog, ->(opts = {}) {
9
+ opts[:dog] = {} unless opts[:dog]
10
+ self.new(opts) { |p| p.adopt_a_dog(opts[:dog]) }
11
+ }
12
+ lr_scope :young, -> { where('age < 30') }
13
+ lr_scope :short_hair, -> { where('haircut == "short"') }
14
+
15
+ lr_method :speak, -> (string) { puts string }
16
+ lr_method :add_dog, :name, 'dogs << Dog.new(name: name)'
17
+ lr_method :introduce_yourself, 'puts "Hello, my name is #{name}"'
18
+
19
+ lr_validates :name, :age, presence: true
20
+
21
+ def self.make_people(*args, &block)
22
+ opts = args.extract_options!
23
+
24
+ people = args.map do |arg|
25
+ Person.new { |p| p.name = arg }
26
+ end
27
+
28
+ if opts[:count] == true
29
+ puts "There are #{people.size} people!"
30
+ end
31
+
32
+ if opts[:dog]
33
+ people.each do |person|
34
+ person.adopt_a_dog(opts[:dog]) do |d|
35
+ d.name = "#{person.name}'s best friend"
36
+ end
37
+ end
38
+ end
39
+
40
+ people.each { |person| block.call(person) } if block
41
+
42
+ people
43
+ end
44
+
45
+ def times(num, &block)
46
+ if block
47
+ i = 0
48
+ while i < num
49
+ block.call
50
+ i += 1
51
+ end
52
+ i
53
+ else
54
+ self
55
+ end
56
+ end
57
+
58
+ def adopt_a_dog(opts = {}, &block)
59
+ dog = Dog.new(opts)
60
+ block.call(dog) if block
61
+ self.dogs << dog
62
+ dog
63
+ end
64
+
65
+ JOE = new_with_dog(
66
+ name: 'Joe',
67
+ age: 35,
68
+ haircut: 'short',
69
+ dog: {
70
+ cute: true,
71
+ name: 'Frank',
72
+ breed: 'Schnauzer',
73
+ weight: 45,
74
+ friend: true,
75
+ years_known: 6
76
+ }
77
+ ).freeze
78
+ end
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['msimonborg@gmail.com']
12
12
 
13
13
  spec.summary = 'Some ActiveRecord magic for your table-less POROs. WIP.'
14
- spec.description = 'Add some convenience macros for your POROs that cut down on boilerplate code. Method definition macros, more powerful attr_accessors, and easy associations between in-memory objects. Somewhat mocks the ActiveRecord API to make it feel comfortable and intuitive for Rails developers.'
14
+ spec.description = 'Add some convenience macros for your POROs that cut down on boilerplate code. Method definition macros, more powerful attr_accessors, and easy associations between in-memory objects. Somewhat mocks the ActiveRecord API to make it feel comfortable and intuitive for Rails developers. The main intent of this project is to explore dynamic programming in Ruby. Maybe someone will find it useful. WIP.'
15
15
  spec.homepage = 'https://www.github.com/msimonborg/lazy_record'
16
16
  spec.license = 'MIT'
17
17
  spec.add_dependency 'activesupport'
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'active_support'
3
+ require 'active_support/inflector'
2
4
  require 'lazy_record/version'
3
5
  require 'lazy_record/associations'
4
6
  require 'lazy_record/attributes'
@@ -11,4 +13,4 @@ require 'lazy_record/relation'
11
13
  require 'lazy_record/base'
12
14
 
13
15
  # Namespace
14
- class LazyRecord; end
16
+ module LazyRecord; end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Set up in-memory associations between POROs.
4
4
  module Associations
5
5
  COLLECTION_MODULE_NAME = :Collections
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Add special attr_accessors that automatically add initialization options
4
4
  # to your initialize method. Using lr_attr_accessor, you automatically get
5
5
  # an #initialize method that takes setter options for each attribute and
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Inherit from LazyRecord::Base to achieve lazier development.
4
4
  #
5
5
  # LazyRecord gives you some ActiveRecord-style conveniences for your in-memory
@@ -37,7 +37,7 @@ class LazyRecord
37
37
  @all.where(condition)
38
38
  end
39
39
 
40
- def initialize
40
+ def initialize(_opts = {})
41
41
  yield self if block_given?
42
42
  end
43
43
 
@@ -56,7 +56,8 @@ class LazyRecord
56
56
  private :id=
57
57
 
58
58
  def inspect
59
- "#<#{self.class} id: #{id ? id : 'nil'}, #{instance_attrs_to_s.join(', ')}>"
59
+ "#<#{self.class} id: #{id ? id : 'nil'}"\
60
+ "#{instance_attrs_to_s.unshift('').join(', ')}>"
60
61
  end
61
62
  end
62
63
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # After #initialize callbacks for validations and setting object id.
4
4
  module Callbacks
5
5
  def inherited(klass)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Generate dynamic modules for dynamic methods created with #define_method,
4
4
  # for insertion into inheritance chain. This allows you to make calls to
5
5
  # super for these methods.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Macro for dynamic instance method generation. Best to use for one-liners.
4
4
  module Methods
5
5
  METHODS_MODULE_NAME = :DynamicMethods
@@ -1,32 +1,47 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
3
- class Relation < Array
4
- attr_reader :model
2
+ module LazyRecord
3
+ class Relation
4
+ include Enumerable
5
+
6
+ attr_reader :model, :all
5
7
 
6
8
  def initialize(model:, array: nil)
7
9
  raise ArgumentError, "model must be a class" unless model.is_a?(Class)
8
10
  @model = model
11
+ @all = []
9
12
  self_extend_scopes_module
10
- array.each { |object| self << object } if array
13
+ array.each { |object| @all << object } if array
11
14
  end
12
15
 
13
16
  def <<(other)
14
17
  unless other.is_a?(model)
15
18
  raise ArgumentError, "object must be of type #{model}"
16
19
  else
17
- super
20
+ all << other
18
21
  end
19
22
  end
20
23
 
21
24
  def inspect
22
- "\#<#{model}Relation [#{self.map(&:inspect).join(', ')}]>"
25
+ "\#<#{model}Relation [#{all.map(&:inspect).join(', ')}]>"
23
26
  end
24
27
 
25
28
  def where(condition)
26
- result = select { |x| eval "x.#{condition}" }
29
+ result = all.select { |x| eval "x.#{condition}" }
27
30
  self.class.new(model: model, array: result)
28
31
  end
29
32
 
33
+ def each(&block)
34
+ all.each(&block)
35
+ end
36
+
37
+ def [](index)
38
+ all[index]
39
+ end
40
+
41
+ def last
42
+ self[-1]
43
+ end
44
+
30
45
  def self_extend_scopes_module
31
46
  if model.const_defined?(:ScopeMethods, _search_ancestors = false)
32
47
  mod = eval("#{model}::ScopeMethods")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Add ActiveRecord-style scope macros to your classes. Allows chaining.
4
4
  module Scopes
5
5
  SCOPE_MODULE_NAME = :ScopeMethods
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
2
+ module LazyRecord
3
3
  # Validations callbacks. If validations don't pass then initialization
4
4
  # will return false.
5
5
  module Validations
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
- class LazyRecord
3
- VERSION = '0.1.1'
2
+ module LazyRecord
3
+ VERSION = '0.1.2'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - M. Simon Borg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-06 00:00:00.000000000 Z
11
+ date: 2017-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -83,7 +83,8 @@ dependencies:
83
83
  description: Add some convenience macros for your POROs that cut down on boilerplate
84
84
  code. Method definition macros, more powerful attr_accessors, and easy associations
85
85
  between in-memory objects. Somewhat mocks the ActiveRecord API to make it feel comfortable
86
- and intuitive for Rails developers.
86
+ and intuitive for Rails developers. The main intent of this project is to explore
87
+ dynamic programming in Ruby. Maybe someone will find it useful. WIP.
87
88
  email:
88
89
  - msimonborg@gmail.com
89
90
  executables: []
@@ -101,6 +102,11 @@ files:
101
102
  - Rakefile
102
103
  - bin/console
103
104
  - bin/setup
105
+ - config/environment.rb
106
+ - example/cat.rb
107
+ - example/dog.rb
108
+ - example/friend.rb
109
+ - example/person.rb
104
110
  - lazy_record.gemspec
105
111
  - lib/lazy_record.rb
106
112
  - lib/lazy_record/associations.rb