findable 0.0.3 → 0.1.0

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: ed1492954c92884d4fcf68884ef79e9137607919
4
- data.tar.gz: f7118ffa85352d988fc77af88128246edaf8ebd8
3
+ metadata.gz: 3d9b4cc9d5f9ac6949eaa3db65f5dc97359a8450
4
+ data.tar.gz: 3ca9a61d80ea9b8d74f5a4e2df78082f07083012
5
5
  SHA512:
6
- metadata.gz: c85b0d1bcf815bfa2922e3f4504e8deae8b9b335e5fa0fd889883bf004d263d8a5836df74be55c81517b5d68be82cf55e8144b6535e71fd9e609febdbb460a40
7
- data.tar.gz: 90b596946d2344d52c0ae952bdbc62ed252e1222888d359377c3fd9deb06becf16d31b5e605b560ef889ec29d404be415c6cb371478f93b23026ac043a979729
6
+ metadata.gz: 68ab5f2cb1c8b3fc443ff1b4d1c8199a07634d3fb7ad08a7587247c0ee9836d711f596e2286566f82e59c9dccb2d815098e84e95adc665b0c878d3a721461a1c
7
+ data.tar.gz: fdd8b84c3f07ded341d312cc6bd1117f2f64b61f5dc40865fa36877852c27b17562b6b2ff3bcb086344ec7e61317e01f058300c53d745b635e17658363793060
data/.travis.yml CHANGED
@@ -1,14 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.0
4
- - 2.2.0
5
- branches:
6
- only:
7
- - master
3
+ - 2.1.5
4
+ - 2.2.1
8
5
  gemfile:
9
6
  - Gemfile
10
7
  script: bundle exec rake spec
11
- notifications:
12
- mails:
13
- - i2bskn@gmail.com
8
+ services:
9
+ - redis-server
14
10
 
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in findable.gemspec
4
3
  gemspec
4
+
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage Status](https://img.shields.io/coveralls/i2bskn/findable.svg)](https://coveralls.io/r/i2bskn/findable)
6
6
  [![Code Climate](https://codeclimate.com/github/i2bskn/findable/badges/gpa.svg)](https://codeclimate.com/github/i2bskn/findable)
7
7
 
8
- Redis wrapper with API like ActiveRecord. (While creating...)
8
+ Redis wrapper with API like ActiveRecord.
9
9
 
10
10
  ## Installation
11
11
 
@@ -17,27 +17,79 @@ gem "findable"
17
17
 
18
18
  And then execute:
19
19
 
20
- $ bundle
20
+ ```
21
+ $ bundle
22
+ ```
23
+
24
+ Setup config file and seed script:
25
+
26
+ ```
27
+ $ rails generate findable:install
28
+ ```
29
+
30
+ Added following files:
31
+
32
+ - `config/initializers/findable.rb`
33
+ - Config file for Findable.
34
+ - `db/findable_seeds.rb`
35
+ - Seed script for Findable.
36
+ - `db/findable_seeds/.keep`
37
+ - Directory for storing seed files of Findable.
21
38
 
22
39
  ## Usage
23
40
 
41
+ Create seed file if static data.
42
+
43
+ Example `db/findable_seeds/tags.yml`:
44
+
45
+ ```yaml
46
+ data1:
47
+ id: 1
48
+ name: Ruby
49
+ ```
50
+
51
+ Create model.
52
+
53
+ Example `app/models/tag.rb`:
54
+
24
55
  ```ruby
25
- class Company < ActiveRecord::Base
26
- has_many :person
56
+ class Tag < Findable::Base
27
57
  end
58
+ ```
28
59
 
29
- class Person < Findable::Base
30
- fields :name, :email, :gender, :company_id
31
- belongs_to :company
32
- end
60
+ Execute seed script if you create seed files.
33
61
 
34
- person = Person.new(name: "Ken Iiboshi", gender: "male")
35
- person.email = "i2bskn@example.com"
36
- person.save
62
+ ```
63
+ $ rake findable:seeds
64
+ ```
65
+
66
+ Manipulate data with API like ActiveRecord.
67
+
68
+ ```
69
+ $ rails console
70
+ pry(main)> Tag.find(1)
71
+ => #<Tag:0x00000108068430 @_attributes={:id=>1, :name=>"Ruby"}>
72
+ pry(main)> golang = Tag.create(name: "Go")
73
+ => #<Tag:0x00000107ff6420 @_attributes={:name=>"Go", :id=>2}>
74
+ pry(main)> Tag.all.each {|tag| p tag.name }
75
+ "Ruby"
76
+ "Go"
77
+ => [#<Tag:0x00000107f49568 @_attributes={:id=>1, :name=>"Ruby"}>, #<Tag:0x00000107f492e8 @_attributes={:name=>"Go", :id=>2}>]
78
+ ```
79
+
80
+ ## Associations
81
+
82
+ It is possible to use the `belongs_to` and `has_one` and `has_many`.
83
+ Mutually can refer to objects of ActiveRecord and Findable.
84
+
85
+ ```ruby
86
+ class Article < ActiveRecord::Base
87
+ include Findable::Associations::ActiveRecordExt
88
+ belongs_to :tag
89
+ end
37
90
 
38
- people = Person.where(gender: "male")
39
- people.each do |person|
40
- puts person.name
91
+ class Tag < Findable::Base
92
+ has_many :articles
41
93
  end
42
94
  ```
43
95
 
data/Rakefile CHANGED
@@ -1,10 +1,15 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
+ task :default => :spec
5
+
4
6
  desc "Run all specs"
5
7
  RSpec::Core::RakeTask.new(:spec) do |spec|
6
8
  spec.pattern = FileList['spec/**/*_spec.rb']
7
9
  end
8
10
 
9
- task :default => :spec
11
+ desc "Console with library"
12
+ task :console do
13
+ sh "pry -I lib -r bundler/setup -r findable"
14
+ end
10
15
 
data/findable.gemspec CHANGED
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  lib = File.expand_path('../lib', __FILE__)
2
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
4
  require 'findable/version'
@@ -19,13 +20,13 @@ Gem::Specification.new do |spec|
19
20
 
20
21
  spec.add_dependency "activesupport"
21
22
  spec.add_dependency "activemodel"
22
- spec.add_dependency "redis"
23
+ spec.add_dependency "redis-objects"
23
24
  spec.add_dependency "oj"
24
25
 
25
26
  spec.add_development_dependency "bundler"
26
27
  spec.add_development_dependency "rake"
27
28
  spec.add_development_dependency "rspec-rails"
28
- spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "pry"
29
30
  spec.add_development_dependency "coveralls"
30
31
  end
31
32
 
data/lib/findable.rb CHANGED
@@ -1,13 +1,12 @@
1
1
  require "active_support"
2
2
  require "active_model"
3
- require "redis"
3
+ require "redis/objects"
4
4
  require "oj"
5
5
 
6
6
  require "findable/version"
7
7
  require "findable/errors"
8
8
  require "findable/configuration"
9
- require "findable/serializer"
10
- require "findable/association"
9
+ require "findable/query"
11
10
  require "findable/base"
12
11
  require "findable/railtie" if defined?(Rails)
13
12
 
@@ -0,0 +1,52 @@
1
+ require "findable/associations/utils"
2
+
3
+ module Findable
4
+ module Associations
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def has_many(*args)
9
+ name, options = Utils.parse_args(args)
10
+ model = Utils.model_for(name, collection: true, **options)
11
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
12
+
13
+ define_method(name) do
14
+ model.where(foreign_key => public_send(self.class.primary_key))
15
+ end
16
+ end
17
+
18
+ def has_one(*args)
19
+ name, options = Utils.parse_args(args)
20
+ model = Utils.model_for(name, **options)
21
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
22
+
23
+ define_method(name) do
24
+ model.find_by(foreign_key => public_send(self.class.primary_key))
25
+ end
26
+ end
27
+
28
+ def belongs_to(*args)
29
+ name, options = Utils.parse_args(args)
30
+ model = Utils.model_for(name, safe: true, **options)
31
+ foreign_key = options[:foreign_key].presence || name.to_s.foreign_key
32
+
33
+ if options[:polymorphic]
34
+ define_method(name) do
35
+ public_send("#{name}_type").constantize.find(public_send(foreign_key))
36
+ end
37
+ else
38
+ define_field(foreign_key)
39
+
40
+ define_method(name) do
41
+ model.find_by(model.primary_key => public_send(foreign_key))
42
+ end
43
+
44
+ define_method("#{name}=") do |value|
45
+ attributes[foreign_key.to_sym] = value ? value.public_send(model.primary_key) : nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,87 @@
1
+ module Findable
2
+ module Associations
3
+ module ActiveRecordExt
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_many(*args)
8
+ name, options = Utils.parse_args(args)
9
+ model = Utils.model_for(name, collection: true, safe: true, **options)
10
+
11
+ if model && model < Findable::Base
12
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
13
+
14
+ define_method(name) do
15
+ model.where(foreign_key => public_send(self.class.primary_key))
16
+ end
17
+
18
+ reflection = ActiveRecord::Reflection.create(
19
+ :has_many,
20
+ name.to_sym,
21
+ nil,
22
+ options,
23
+ self,
24
+ )
25
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def has_one(*args)
32
+ name, options = Utils.parse_args(args)
33
+ model = Utils.model_for(name, safe: true, **options)
34
+
35
+ if model && model < Findable::Base
36
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
37
+
38
+ define_method(name) do
39
+ model.find_by(foreign_key => public_send(self.class.primary_key))
40
+ end
41
+
42
+ reflection = ActiveRecord::Reflection.create(
43
+ :has_one,
44
+ name.to_sym,
45
+ nil,
46
+ options,
47
+ self,
48
+ )
49
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def belongs_to(*args)
56
+ name, options = Utils.parse_args(args)
57
+ model = Utils.model_for(name, safe: true, **options)
58
+
59
+ if model && model < Findable::Base
60
+ foreign_key = options[:foreign_key].presence || name.to_s.foreign_key
61
+
62
+ define_method(name) do
63
+ model.find_by(model.primary_key => public_send(foreign_key))
64
+ end
65
+
66
+ define_method("#{name}=") do |value|
67
+ value = value ? value.public_send(model.primary_key) : nil
68
+ public_send("#{foreign_key}=", value)
69
+ end
70
+
71
+ reflection = ActiveRecord::Reflection.create(
72
+ :belongs_to,
73
+ name.to_sym,
74
+ nil,
75
+ options,
76
+ self,
77
+ )
78
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
79
+ else
80
+ super
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,25 @@
1
+ module Findable
2
+ module Associations
3
+ class Utils
4
+ def self.model_for(name, options = {})
5
+ unless model_name = options[:class_name].presence
6
+ name = options[:collection] ? name.to_s.singularize : name.to_s
7
+ model_name = name.camelize
8
+ end
9
+
10
+ if options[:safe]
11
+ model_name.try(:safe_constantize)
12
+ else
13
+ model_name.constantize
14
+ end
15
+ end
16
+
17
+ def self.parse_args(args)
18
+ copied = args.dup
19
+ options = copied.extract_options!
20
+ [copied.first, options]
21
+ end
22
+ end
23
+ end
24
+ end
25
+
data/lib/findable/base.rb CHANGED
@@ -1,81 +1,95 @@
1
- require "findable/recordable"
2
- require "findable/connection"
3
- require "findable/namespace"
1
+ require "findable/associations"
4
2
 
5
3
  module Findable
6
4
  class Base
7
5
  include ActiveModel::Model
8
- include Recordable
9
- include Connection
10
- include Namespace
11
- extend Association
6
+ include ActiveModel::AttributeMethods
7
+
8
+ include Associations
9
+
10
+ attribute_method_suffix "="
11
+ attribute_method_suffix "?"
12
12
 
13
13
  class << self
14
+ ## field definitions
15
+
16
+ def define_field(attr, options = {})
17
+ unless public_method_defined?(attr)
18
+ options.symbolize_keys!
19
+ define_attribute_methods attr
20
+ define_method(attr) { attributes[attr.to_sym] }
21
+ column_names << attr.to_sym
22
+ end
23
+ end
24
+
25
+ ## ActiveRecord like APIs
26
+
14
27
  def primary_key
15
28
  "id"
16
29
  end
17
30
 
18
31
  def column_names
19
- raise NotImplementedError
32
+ @_column_names ||= [:id]
20
33
  end
21
34
 
22
35
  def all
23
- all_data.map {|data| new(data) }
36
+ data.map {|val| new(val) }
24
37
  end
25
38
 
26
- def find(*ids)
27
- ids = ids.first if ids.size == 1
28
- values = find_by_id(ids)
29
-
30
- case
31
- when values.empty? then nil
32
- when ids.is_a?(Array) then values.map {|val| new(val)}
33
- else new(values.first)
39
+ def find(ids)
40
+ if values = find_by_ids(ids).compact.presence
41
+ ids.is_a?(Array) ? values.map {|val| new(val) } : new(values.first)
42
+ else
43
+ raise RecordNotFound.new(self, id: ids)
34
44
  end
35
45
  end
36
46
 
37
- def find_by(params)
38
- params.symbolize_keys!
39
- if id = params.delete(:id)
40
- values = find_by_id(id)
41
- return nil if values.empty?
42
- return new(values.first) if params.empty?
43
-
44
- values.each {|val|
45
- record = new(val)
46
- return record if params.all? {|k,v| record.send(k) == v }
47
- }
47
+ def find_by(conditions)
48
+ if conditions.is_a?(Hash)
49
+ conditions.symbolize_keys!
50
+ if id = conditions.delete(:id)
51
+ values = find_by_ids(id).compact
52
+ case
53
+ when values.empty? then nil
54
+ when conditions.empty? then new(values.first)
55
+ else
56
+ value = values.detect {|val|
57
+ record = new(val)
58
+ conditions.all? {|k, v| record.public_send(k) == v }
59
+ }
60
+ value ? new(value) : nil
61
+ end
62
+ else
63
+ all.detect {|r|
64
+ conditions.all? {|k, v| r.public_send(k) == v }
65
+ }
66
+ end
48
67
  else
49
- all_data.each {|val|
50
- record = new(val)
51
- return record if params.all? {|k,v| record.send(k) == v }
52
- }
68
+ values = find_by_ids(conditions).compact
69
+ values.empty? ? nil : new(values.first)
53
70
  end
54
71
  end
55
72
 
56
- def where(params)
57
- all.select {|record| params.all? {|k,v| record.send(k) == v } }
73
+ def find_by!(conditions)
74
+ find_by(conditions.dup) || (raise RecordNotFound.new(self, conditions))
58
75
  end
59
76
 
60
- def exists?(record)
61
- if record.is_a?(self)
62
- _id = record.id
63
- return false unless _id
77
+ def where(conditions)
78
+ if id = conditions.delete(:id)
79
+ values = find_by_ids(id).compact
80
+ if conditions.empty?
81
+ values.map {|val| new(val) }
82
+ else
83
+ values.map {|val|
84
+ record = new(val)
85
+ conditions.all? {|k, v| record.public_send(k) == v } ? record : nil
86
+ }.compact
87
+ end
64
88
  else
65
- _id = record.to_i
89
+ all.select {|r|
90
+ conditions.all? {|k, v| r.public_send(k) == v }
91
+ }
66
92
  end
67
-
68
- redis.hexists data_key, _id
69
- end
70
-
71
- delegate :first, :last, to: :all
72
-
73
- def count
74
- redis.hlen(data_key)
75
- end
76
-
77
- def ids
78
- redis.hkeys(data_key).map(&:to_i)
79
93
  end
80
94
 
81
95
  def create(attrs = {})
@@ -83,53 +97,99 @@ module Findable
83
97
  record.save
84
98
  record
85
99
  end
100
+ alias_method :create!, :create
86
101
 
87
- def create!(attrs = {})
88
- record = new(attrs)
89
- record.save!
90
- record
102
+ [:first, :last].each do |m|
103
+ define_method(m) do
104
+ value = self.data.public_send(m)
105
+ value ? new(value) : nil
106
+ end
91
107
  end
92
108
 
93
- def delete_all
94
- redis.del(data_key)
95
- end
109
+ ## Query APIs
110
+
111
+ delegate :find_by_ids, :data, to: :query
112
+ delegate :count, :ids, :delete_all, to: :query
96
113
  alias_method :destroy_all, :delete_all
97
114
 
98
- def transaction(&block)
99
- redis.multi &block
115
+ def exists?(obj)
116
+ if _id = id_from(obj)
117
+ query.exists?(data_key, _id)
118
+ else
119
+ false
120
+ end
100
121
  end
101
122
 
102
- def import(records)
103
- data = records.each_with_object([]) {|record, obj|
104
- record.id ||= auto_incremented_id
105
- obj << record.id
106
- obj << record.to_json
107
- }
108
- redis.hmset(data_key, *data)
123
+ def insert(obj)
124
+ query.insert(obj.attributes)
109
125
  end
110
126
 
111
- def insert(record)
112
- record.id ||= auto_incremented_id
113
- redis.hset(data_key, record.id, record.to_json)
127
+ def delete(obj)
128
+ if _id = id_from(obj)
129
+ query.delete(_id)
130
+ end
114
131
  end
115
132
 
116
- def delete(id)
117
- redis.hdel(data_key, id)
133
+ def query
134
+ @_query ||= Query.new(self)
118
135
  end
119
136
 
120
137
  private
121
- def auto_incremented_id
122
- redis.hincrby(info_key, :auto_inclement, 1)
138
+ def id_from(obj)
139
+ obj.is_a?(self) ? obj.id : obj.to_i
123
140
  end
141
+ end
124
142
 
125
- def all_data
126
- redis.hvals(data_key)
127
- end
143
+ def initialize(params = {})
144
+ params = Oj.load(params) if params.is_a?(String)
145
+ params.symbolize_keys!
146
+ params.keys.each {|attr| self.class.define_field(attr) }
147
+ @_attributes = params
148
+ end
128
149
 
129
- def find_by_id(id)
130
- redis.hmget(data_key, *Array(id)).compact
131
- end
150
+ def id
151
+ attributes[:id].presence
152
+ end
153
+
154
+ def id=(value)
155
+ attributes[:id] = value
156
+ end
157
+
158
+ def hash
159
+ id.hash
160
+ end
161
+
162
+ def new_record?
163
+ id ? !self.class.exists?(self) : true
132
164
  end
165
+
166
+ def persisted?
167
+ !new_record?
168
+ end
169
+
170
+ def save
171
+ @_attributes = self.class.insert(self)
172
+ self
173
+ end
174
+ alias_method :save!, :save
175
+
176
+ def delete
177
+ self.class.delete(self)
178
+ end
179
+ alias_method :destroy, :delete
180
+
181
+ def attributes
182
+ @_attributes ||= {}
183
+ end
184
+
185
+ private
186
+ def attribute=(attr, value)
187
+ attributes[attr.to_sym] = value
188
+ end
189
+
190
+ def attribute?(attr)
191
+ attributes[attr.to_sym].present?
192
+ end
133
193
  end
134
194
  end
135
195