seria 1.0.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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjU2OGRiY2JlMjQ2OTA3YTFkM2RlNDUxODUzZWUyNjQ5YmEyNjlmNg==
5
+ data.tar.gz: !binary |-
6
+ M2JkYmZhZjYxYmViNTRiNzA0ZWQ4YTc2ZGYyNzgwNTg5MTA5MDUwZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTVmODUyYTM0YTc3OWQzYjI4OTA2N2U5ZDUzODVhMjY4MTk2ZjE4OGExYTEx
10
+ OGNmOTY1NDNjNDVjZDcyYWY4NGYwY2VhNzEwZGJkNjQwN2E4MjY3MjM4NGE5
11
+ NTI2YWM1OTViZjVhMjNiMWZlOTMxZDdmZTE4MGFjNDBkZmJmNjM=
12
+ data.tar.gz: !binary |-
13
+ OGVjOGVmNTZkMDUyZDYyNDFmOTFiYTc2MDhhMzYwN2M4NjU5Mjc3MzdhNzUw
14
+ MDc5YjRjODVmMjMwZTYxNzU3ZWQwZDcyZjJmMDMzZGU0MTg4MjJiM2FkN2Qw
15
+ OWRjY2NjYzNmNmY3YmYxNmZkYmIyMDhiMWQxMjU5NzI3ZWI1MWE=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sparky.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ronna
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Seria
2
+
3
+ I wrote this gem because I needed to be able to load my ARs with key-value attributes without running migrations,
4
+ in a similar way to [dynamic_attributes](https://github.com/moiristo/dynamic_attributes) gem.
5
+ But I also needed to be able to query the data, so storing it in a JSON wasn't a good enough solution.
6
+
7
+ Seria lets you add completely dynamic data to your ARs.
8
+ Seria doesn't store your hash data in a JSON, but in separate records making it queryable.
9
+ Seria will automatically cast your attributes back to their original type when loaded from the database -
10
+ no more messy string manipulations.
11
+
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'seria'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install seria
26
+
27
+ ## Usage
28
+
29
+ ### Generate the info table
30
+
31
+ $ rails g info_table book
32
+ $ rake db:migrate
33
+
34
+ ### Register your info table owner and start loading attributes
35
+
36
+ ```ruby
37
+
38
+ class Book
39
+ include Seria::InfoTableOwner
40
+ end
41
+
42
+ book = Book.new(title: "The portable")
43
+ book.infos["price"]=100.0
44
+ book.infos["author"] = 'Dorothy Paker'
45
+ book.infos["recommended"] = true
46
+ book.save
47
+
48
+ book = Book.last
49
+ book.infos["price"]
50
+ => 100.0
51
+ book.infos["recommended"]
52
+ => true
53
+
54
+ ```
55
+
56
+ ### Querying
57
+
58
+ Seria stores your data in separate table with the fields foreign_id, field_name, field_value and field_type
59
+
60
+ ```sql
61
+ /* The values are saved as strings, so when querying the database you need to convert to your original type: */
62
+ select CAST(field_value as decimal(10,2)) as price from book_infos where field_name = "price"
63
+ ```
64
+
65
+ ```ruby
66
+ class BookInfo #yes, you can define yourself
67
+ scope :prices, proc{ where('field_name="price"') }
68
+ end
69
+
70
+ cheep_books = BookInfo.prices.where('CAST(field_value as decimal(10,2)) < 10') #database
71
+ cheep_books = BookInfo.prices.select{|p| p.field_value < 10} #memory
72
+
73
+ ```
74
+
75
+ ### Configure your own table-names, fields and converters
76
+
77
+ ```ruby
78
+ Seria.configure do |config|
79
+ config.fields.key = :fkey
80
+ config.fields.value = :fvalue
81
+ config.fields.type = :ftype
82
+ config.descriptor = :data
83
+ config.converters["price"] = Seria::BigDecimalConverter
84
+ end
85
+ ```
86
+
87
+
88
+
89
+ ## Contributing
90
+
91
+ 1. Fork it
92
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
93
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
94
+ 4. Push to the branch (`git push origin my-new-feature`)
95
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,32 @@
1
+ require 'rails/generators/named_base'
2
+ module Seria
3
+ module Generators
4
+ module Migration
5
+ class InfoTableGenerator < Rails::Generators::NamedBase
6
+ include Rails::Generators::Migration
7
+ namespace "info_table"
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_migration
11
+ migration_template(
12
+ "#{self.class.generator_name}.rb",
13
+ "db/migrate/create_#{file_name}_#{suffix}.rb"
14
+ )
15
+ end
16
+
17
+ def self.next_migration_number(dirname) #:nodoc:
18
+ next_migration_number = current_migration_number(dirname) + 1
19
+ if ActiveRecord::Base.timestamped_migrations
20
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
21
+ else
22
+ "%.3d" % next_migration_number
23
+ end
24
+ end
25
+
26
+ def suffix
27
+ Seria.table_suffix
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :<%= migration_class_name.underscore.downcase.gsub(/^create_/,'')%> do |t|
4
+ t.string :<%= Seria.config.fields.key %>
5
+ t.string :<%= Seria.config.fields.value %>
6
+ t.string :<%= Seria.config.fields.type %>
7
+ t.integer :<%=migration_class_name.underscore.downcase.gsub(/^create_/,'').gsub(/_#{suffix}$/,'')%>_id
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,69 @@
1
+ module Seria
2
+ class DefaultConverter
3
+ attr_reader :value, :type
4
+ def initialize(value, type)
5
+ @value, @type = value, type
6
+ end
7
+
8
+ def klass
9
+ if type.present?
10
+ type
11
+ else
12
+ value.class.to_s
13
+ end
14
+ end
15
+
16
+ def conversions
17
+ @conversions ||=
18
+ {
19
+ 'Fixnum' => lambda { value.to_i },
20
+ 'BigDecimal' => lambda { value.to_d },
21
+ 'Float' => lambda { value.to_f },
22
+ 'NilClass' => nil,
23
+ 'FalseClass' => false,
24
+ 'TrueClass' => true,
25
+ 'String' => lambda { value.to_s },
26
+ 'Time' => lambda { Time.parse(value) },
27
+ 'DateTime' => lambda { DateTime.parse(value) },
28
+ 'Date' => lambda { Date.parse(value) },
29
+ 'ActiveSupport::TimeWithZone' => lambda { Time.zone.parse(value) }
30
+ }
31
+ end
32
+
33
+ def convert
34
+ if value.class.to_s != klass
35
+ conversion = conversions[klass]
36
+ if conversion.is_a?(Proc)
37
+ conversion.call
38
+ else
39
+ conversion
40
+ end
41
+ else
42
+ value
43
+ end
44
+ end
45
+
46
+ def to_db
47
+ value
48
+ end
49
+ end
50
+
51
+ class BigDecimalConverter
52
+ attr_reader :value, :type
53
+ def initialize(value, type)
54
+ @value, @type = value, type
55
+ end
56
+
57
+ def convert
58
+ if value && !value.is_a?(BigDecimal)
59
+ value.to_d
60
+ else
61
+ value
62
+ end
63
+ end
64
+
65
+ def to_db
66
+ value
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,116 @@
1
+ require "seria/version"
2
+ require 'seria/converters'
3
+
4
+ module Seria
5
+
6
+ module InfoTable
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ klass_name = self.name.gsub(Seria.descriptor.camelize, '')
12
+ belongs_to klass_name.downcase.to_sym
13
+ alias_method :owner, klass_name.downcase.to_sym
14
+
15
+ before_save :to_db
16
+ attr_reader :in_memory
17
+ end
18
+
19
+ def convert
20
+ converter.convert
21
+ end
22
+
23
+ def converters
24
+ Seria.config.converters || {}
25
+ end
26
+
27
+ def converter
28
+ (converters[field_name] ||
29
+ converters[field_type] ||
30
+ DefaultConverter).new(
31
+ read_attribute(Seria.config.fields.value),
32
+ read_attribute(Seria.config.fields.type))
33
+ end
34
+
35
+ def field_name
36
+ read_attribute Seria.config.fields.key
37
+ end
38
+
39
+ def field_value
40
+ convert
41
+ end
42
+ def field_type
43
+ read_attribute(Seria.config.fields.type)
44
+ end
45
+ def field_type=(val)
46
+ write_attribute(Seria.config.fields.type, val)
47
+ end
48
+ def field_value=(val)
49
+ write_attribute(Seria.config.fields.value, val)
50
+ end
51
+
52
+ def to_db
53
+ self.field_value = convert #force cast back from varchar in case not a new entry
54
+ self.field_type = read_attribute(Seria.config.fields.value).class.to_s
55
+ self.field_value = converter.to_db
56
+ true
57
+ end
58
+
59
+ def self.define_info_table(class_name)
60
+ if !eval("defined?(#{class_name}) && #{class_name}.is_a?(Class)")
61
+ Object.const_set class_name, Class.new(ActiveRecord::Base)
62
+ end
63
+ klass = class_name.constantize
64
+ klass.send(:include, Seria::InfoTable) unless klass.include? Seria::InfoTable
65
+ if Rails.version =~ /^3/
66
+ klass.send(:attr_accessible, *(Seria.config.fields.marshal_dump.values))
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ module InfoTableOwner
73
+
74
+ extend ActiveSupport::Concern
75
+
76
+ included do
77
+ InfoTable::define_info_table class_name
78
+
79
+ has_many class_name.tableize.to_sym, class_name: class_name, :autosave => true do
80
+
81
+ def []= key, val
82
+ info = lookup(key)
83
+ if info
84
+ info.field_value = val
85
+ info.field_type = val.class.name
86
+ else
87
+ build(
88
+ Seria.config.fields.key => key,
89
+ Seria.config.fields.value => val,
90
+ Seria.config.fields.type => val.class.name
91
+ )
92
+ end
93
+ val
94
+ end
95
+ def [] key
96
+ info = lookup(key)
97
+ info.field_value if info
98
+ end
99
+ def lookup key
100
+ to_a.select{|i| i.field_name == key}.first
101
+ end
102
+ end
103
+ alias_method :my_infos, class_name.tableize.to_sym
104
+ alias_method Seria.table_suffix.to_sym, class_name.tableize.to_sym
105
+ end
106
+
107
+ module ClassMethods
108
+
109
+ def class_name
110
+ "#{self.to_s}#{Seria.descriptor.camelize}"
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ module Seria
2
+ VERSION = "1.0.0"
3
+ end
data/lib/seria.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "seria/version"
2
+ require "seria/info_table"
3
+ require 'ostruct'
4
+ module Seria
5
+ @config = OpenStruct.new
6
+ @config.descriptor = :info
7
+ @config.converters = {}
8
+ @config.fields = OpenStruct.new(
9
+ {
10
+ key: :field_name,
11
+ value: :field_value,
12
+ type: :field_type
13
+ })
14
+ def self.configure
15
+ yield @config
16
+ end
17
+
18
+ def self.descriptor
19
+ config.descriptor.to_s
20
+ end
21
+ def self.table_suffix
22
+ descriptor.tableize
23
+ end
24
+ def self.config
25
+ @config
26
+ end
27
+ end
data/seria.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'seria/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "seria"
8
+ spec.version = Seria::VERSION
9
+ spec.authors = ["Ronna"]
10
+ spec.email = [""]
11
+ spec.description = %q{}
12
+ spec.summary = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rails", "> 3.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "sqlite3"
27
+ end
@@ -0,0 +1,62 @@
1
+ require 'rspec'
2
+ require 'active_record'
3
+ require 'active_support'
4
+
5
+ require_relative '../lib/seria'
6
+
7
+ ActiveRecord::Base.establish_connection(
8
+ adapter: 'sqlite3',
9
+ encoding: 'utf8',
10
+ database: ":memory:")
11
+
12
+ class User < ActiveRecord::Base
13
+ include Seria::InfoTableOwner
14
+ end
15
+ Time.zone = "Berlin"
16
+ describe Seria::DefaultConverter do
17
+
18
+ examples = {
19
+ 1 => Fixnum,
20
+ true => TrueClass,
21
+ false => FalseClass,
22
+ 1.0 => Float,
23
+ 1.to_d => BigDecimal,
24
+ nil => NilClass,
25
+ "cool" => String,
26
+ Date.yesterday => Date,
27
+ Time.now => Time,
28
+ DateTime.now => DateTime,
29
+ Time.zone.now => ActiveSupport::TimeWithZone
30
+ }
31
+
32
+ examples.each do |value, klass|
33
+ describe :klass do
34
+ it "should return class #{klass} when no type given" do
35
+ Seria::DefaultConverter.new(value, nil).klass.should == klass.to_s
36
+ end
37
+ end
38
+
39
+ describe :convert do
40
+ it "should convert #{value} from String to #{klass}" do
41
+ Seria::DefaultConverter.new(value.to_s, klass.to_s).convert.class.should == klass
42
+ end
43
+ it "should return #{klass} to it's original value" do
44
+ val = Seria::DefaultConverter.new(value.to_s, klass.to_s).convert
45
+ val.class.should == klass
46
+ val.should == value
47
+ end
48
+ it "returns original #{klass} when already converted" do
49
+ val = Seria::DefaultConverter.new(value, klass.to_s).convert
50
+ val.class.should == klass
51
+ val.should == value
52
+ end
53
+ it "returns original #{klass} when no type given" do
54
+ val = Seria::DefaultConverter.new(value, nil).convert
55
+ val.class.should == klass
56
+ val.should == value
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seria
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ronna
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>'
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>'
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ''
84
+ email:
85
+ - ''
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - lib/generators/info_table_generator.rb
96
+ - lib/generators/templates/info_table.rb
97
+ - lib/seria.rb
98
+ - lib/seria/converters.rb
99
+ - lib/seria/info_table.rb
100
+ - lib/seria/version.rb
101
+ - seria.gemspec
102
+ - spec/converters_spec.rb
103
+ homepage: ''
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.2.2
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: ''
127
+ test_files:
128
+ - spec/converters_spec.rb