DimaD-daylite-models 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.
- data/History.txt +4 -0
- data/LICENSE +22 -0
- data/Manifest.txt +25 -0
- data/README.markdown +47 -0
- data/Rakefile +42 -0
- data/app/models/daylite.rb +31 -0
- data/app/models/daylite/contact.rb +3 -0
- data/app/models/daylite/organization.rb +17 -0
- data/app/models/daylite/organization_task_join.rb +4 -0
- data/app/models/daylite/project.rb +24 -0
- data/app/models/daylite/project_task_join.rb +4 -0
- data/app/models/daylite/role.rb +10 -0
- data/app/models/daylite/role_type.rb +2 -0
- data/app/models/daylite/task.rb +17 -0
- data/app/models/daylite/user.rb +10 -0
- data/config/database.yml.exmp +9 -0
- data/daylite_models.gemspec +38 -0
- data/daylite_models.rb +17 -0
- data/init.rb +8 -0
- data/install.rb +1 -0
- data/lib/active_record/connection_adapters/openbase_adapter.rb +515 -0
- data/tasks/users_plugin_tasks.rake +4 -0
- data/test/daylite_models_plugin_test.rb +8 -0
- data/test/test_helper.rb +3 -0
- data/uninstall.rb +1 -0
- metadata +105 -0
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2009 Dmitriy Dzema, Infonium Inc
|
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 NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.markdown
|
5
|
+
Rakefile
|
6
|
+
app/models/daylite.rb
|
7
|
+
app/models/daylite/contact.rb
|
8
|
+
app/models/daylite/organization.rb
|
9
|
+
app/models/daylite/organization_task_join.rb
|
10
|
+
app/models/daylite/project.rb
|
11
|
+
app/models/daylite/project_task_join.rb
|
12
|
+
app/models/daylite/role.rb
|
13
|
+
app/models/daylite/role_type.rb
|
14
|
+
app/models/daylite/task.rb
|
15
|
+
app/models/daylite/user.rb
|
16
|
+
config/database.yml.exmp
|
17
|
+
daylite_models.gemspec
|
18
|
+
daylite_models.rb
|
19
|
+
init.rb
|
20
|
+
install.rb
|
21
|
+
lib/active_record/connection_adapters/openbase_adapter.rb
|
22
|
+
tasks/users_plugin_tasks.rake
|
23
|
+
test/daylite_models_plugin_test.rb
|
24
|
+
test/test_helper.rb
|
25
|
+
uninstall.rb
|
data/README.markdown
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
DayliteModels
|
2
|
+
===========
|
3
|
+
|
4
|
+
This is a set of ActiveRecord classes, designed to work with Daylite 3 by [Marketcircle](www.marketcircle.com).
|
5
|
+
|
6
|
+
All the classes are in namespace Daylite. For example Daylite projects live in the class Daylite::Project.
|
7
|
+
|
8
|
+
Models attributes are mapped to usual ruby underscore_notation from Daylite cameCase notation.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
=======
|
12
|
+
|
13
|
+
You can use this module as rails plugin and as a standalone library.
|
14
|
+
|
15
|
+
Usage as Rails plugin
|
16
|
+
---------------------
|
17
|
+
|
18
|
+
Install the plugin
|
19
|
+
|
20
|
+
./script/plugin install <path_to_repo>
|
21
|
+
|
22
|
+
|
23
|
+
Add _daylite_ section to you database.yml
|
24
|
+
|
25
|
+
daylite:
|
26
|
+
adapter: openbase
|
27
|
+
database: MyOrganization
|
28
|
+
host: localhost
|
29
|
+
username: my_username
|
30
|
+
password: my_password
|
31
|
+
|
32
|
+
|
33
|
+
Add the following line to you environment.rb
|
34
|
+
|
35
|
+
Daylite::Base.establish_connection(:daylite)
|
36
|
+
|
37
|
+
You are ready to rock! Now you can use Daylite classes in you application as usual Rails models.
|
38
|
+
|
39
|
+
Usage as a standalone library
|
40
|
+
-----------------------------
|
41
|
+
The easiest way is to create file config/database.yml relative to you running script and place _daylite_ config in it (see Usage as Rails plugin for example). After this you need to place
|
42
|
+
|
43
|
+
require 'daylite_models'
|
44
|
+
|
45
|
+
somewhere in you initialization code and you are done.
|
46
|
+
|
47
|
+
Another way is to provide connection info directly through Daylite::Base.establish_connection call. You will need to provide adapter, database, host, username and password params. See ActiveRecord::Base.establish_connection and Openbase documentation for more info.
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
desc 'Generate documentation for the daylite_models plugin.'
|
7
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
8
|
+
rdoc.rdoc_dir = 'rdoc'
|
9
|
+
rdoc.title = 'DayliteModels'
|
10
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
11
|
+
rdoc.rdoc_files.include('README.markdown')
|
12
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'daylite_models'
|
17
|
+
require 'hoe'
|
18
|
+
|
19
|
+
#oh, shit
|
20
|
+
class Hoe
|
21
|
+
def extra_deps
|
22
|
+
@extra_deps.reject do |x|
|
23
|
+
Array(x).first == 'hoe'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Hoe.new('daylite-models', DayliteModels::VERSION) do |s|
|
29
|
+
s.name = "daylite-models"
|
30
|
+
s.version = "0.1.0"
|
31
|
+
s.author = "Dmitriy Dzema"
|
32
|
+
s.email = "dimad.ag@gmail.com"
|
33
|
+
s.summary = "Set of ActiveRecord classes to work with the Daylite 3 database"
|
34
|
+
|
35
|
+
s.rubyforge_name = 'daylite-models'
|
36
|
+
s.remote_rdoc_dir = '' # Release to root
|
37
|
+
s.need_zip = true
|
38
|
+
s.extra_deps = [ ["activesupport", ">= 2.0.0"], ["activerecord", ">= 2.0.0"], ["activerecord", ">= 0.8.3"]]
|
39
|
+
end
|
40
|
+
rescue LoadError => e
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Daylite
|
2
|
+
end
|
3
|
+
|
4
|
+
class Daylite::Base < ActiveRecord::Base
|
5
|
+
self.abstract_class = true
|
6
|
+
self.pluralize_table_names = false
|
7
|
+
|
8
|
+
set_primary_key "_rowid"
|
9
|
+
set_table_name do
|
10
|
+
self.name.sub("Daylite::", "")
|
11
|
+
end
|
12
|
+
|
13
|
+
# don't allow to save Daylite::Base objects to DB
|
14
|
+
def readonly?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(symbol, *args)
|
19
|
+
guess_attribute = symbol.to_s.camelize(:lower).sub("Id", "ID")
|
20
|
+
if result = attributes[guess_attribute]
|
21
|
+
self.class.class_eval do
|
22
|
+
define_method symbol do
|
23
|
+
return attributes[guess_attribute]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Daylite::Organization < Daylite::Base
|
2
|
+
belongs_to :owner, :class_name => "User", :foreign_key => "assignedToID"
|
3
|
+
|
4
|
+
has_many :organization_task_joins, :foreign_key => "organizationID"
|
5
|
+
has_many :tasks, :through => :organization_task_joins
|
6
|
+
|
7
|
+
has_many :roles, :foreign_key => "organizationID"
|
8
|
+
has_many :projects, :through => :roles
|
9
|
+
|
10
|
+
def projects_tasks
|
11
|
+
projects.collect { |p| p.tasks }.flatten
|
12
|
+
end
|
13
|
+
|
14
|
+
def all_tasks
|
15
|
+
projects_tasks + tasks
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Daylite::Project < Daylite::Base
|
2
|
+
belongs_to :owner, :class_name => "User", :foreign_key => "assignedToID"
|
3
|
+
|
4
|
+
has_many :roles, :foreign_key => "projectID"
|
5
|
+
has_many :projects, :through => :roles
|
6
|
+
|
7
|
+
has_many :project_task_joins, :foreign_key => "projectID"
|
8
|
+
has_many :tasks, :through => :project_task_joins
|
9
|
+
|
10
|
+
def status_codes
|
11
|
+
@@status_codes ||= {
|
12
|
+
0 => "In Progress",
|
13
|
+
1 => "Deffered",
|
14
|
+
2 => "Canceled",
|
15
|
+
3 => "Abandoned",
|
16
|
+
4 => "New",
|
17
|
+
7 => "Done",
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def status_string
|
22
|
+
status_codes[attributes["statusCode"]] || "Unknown status"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Daylite::Task < Daylite::Base
|
2
|
+
belongs_to :owner, :class_name => "User", :foreign_key => "assignedToID"
|
3
|
+
|
4
|
+
has_many :organization_task_joins, :foreign_key => "taskID"
|
5
|
+
has_many :organizations, :through => :organization_task_joins
|
6
|
+
|
7
|
+
has_many :project_task_joins, :foreign_key => "taskID"
|
8
|
+
has_many :projects, :through => :project_task_joins
|
9
|
+
|
10
|
+
def organization
|
11
|
+
organizations[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#<Daylite::Task:#{title}>"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Daylite::User < Daylite::Base
|
2
|
+
belongs_to :contact, :foreign_key => "contactID"
|
3
|
+
|
4
|
+
has_many :organizations, :foreign_key => "assignedToID"
|
5
|
+
has_many :tasks, :class_name => "Task", :foreign_key => "assignedToID"
|
6
|
+
|
7
|
+
def name
|
8
|
+
contact.cachedName
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{daylite-models}
|
3
|
+
s.version = "0.1.0"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Dmitriy Dzema"]
|
7
|
+
s.date = %q{2009-02-11}
|
8
|
+
s.email = %q{dimad.ag@gmail.com}
|
9
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
10
|
+
s.files = ["History.txt", "LICENSE", "Manifest.txt", "README.markdown", "Rakefile", "app/models/daylite.rb", "app/models/daylite/contact.rb", "app/models/daylite/organization.rb", "app/models/daylite/organization_task_join.rb", "app/models/daylite/project.rb", "app/models/daylite/project_task_join.rb", "app/models/daylite/role.rb", "app/models/daylite/role_type.rb", "app/models/daylite/task.rb", "app/models/daylite/user.rb", "config/database.yml.exmp", "daylite_models.gemspec", "daylite_models.rb", "init.rb", "install.rb", "lib/active_record/connection_adapters/openbase_adapter.rb", "tasks/users_plugin_tasks.rake", "test/daylite_models_plugin_test.rb", "test/test_helper.rb", "uninstall.rb"]
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.rdoc_options = ["--main", "README.markdown"]
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.rubyforge_project = %q{daylite-models}
|
15
|
+
s.rubygems_version = %q{1.3.1}
|
16
|
+
s.summary = %q{Set of ActiveRecord classes to work with the Daylite 3 database}
|
17
|
+
s.test_files = ["test/test_helper.rb"]
|
18
|
+
|
19
|
+
if s.respond_to? :specification_version then
|
20
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
21
|
+
s.specification_version = 2
|
22
|
+
|
23
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
24
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.0.0"])
|
25
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 2.0.0"])
|
26
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0.8.3"])
|
27
|
+
s.add_development_dependency(%q<hoe>, [">= 1.8.3"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<activesupport>, [">= 2.0.0"])
|
30
|
+
s.add_dependency(%q<activerecord>, [">= 2.0.0"])
|
31
|
+
s.add_dependency(%q<activerecord>, [">= 0.8.3"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<activesupport>, [">= 2.0.0"])
|
35
|
+
s.add_dependency(%q<activerecord>, [">= 2.0.0"])
|
36
|
+
s.add_dependency(%q<activerecord>, [">= 0.8.3"])
|
37
|
+
end
|
38
|
+
end
|
data/daylite_models.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require 'init'
|
3
|
+
|
4
|
+
module DayliteModels
|
5
|
+
VERSION = '0.1.1'
|
6
|
+
end
|
7
|
+
|
8
|
+
DAYLITE_DATABASE_CONFIG = 'config/database.yml'
|
9
|
+
DAYLITE_CONFIG_SECTION = 'daylite'
|
10
|
+
|
11
|
+
if File.exist?(DAYLITE_DATABASE_CONFIG)
|
12
|
+
yaml = YAML::load(File.read(DAYLITE_DATABASE_CONFIG))
|
13
|
+
|
14
|
+
if yaml[DAYLITE_CONFIG_SECTION]
|
15
|
+
Daylite::Base.establish_connection( yaml[DAYLITE_CONFIG_SECTION] )
|
16
|
+
end
|
17
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
models_path = File.join(File.dirname(__FILE__), 'app', 'models')
|
5
|
+
daylite_path = File.join(models_path, 'daylite')
|
6
|
+
|
7
|
+
$LOAD_PATH << models_path << daylite_path << './lib'
|
8
|
+
ActiveSupport::Dependencies.load_paths << models_path << daylite_path << './lib'
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,515 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Base
|
5
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
6
|
+
def self.openbase_connection(config) # :nodoc:
|
7
|
+
require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
|
8
|
+
|
9
|
+
config = config.symbolize_keys
|
10
|
+
host = config[:host]
|
11
|
+
username = config[:username].to_s
|
12
|
+
password = config[:password].to_s
|
13
|
+
|
14
|
+
|
15
|
+
if config.has_key?(:database)
|
16
|
+
database = config[:database]
|
17
|
+
else
|
18
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
19
|
+
end
|
20
|
+
|
21
|
+
oba = ConnectionAdapters::OpenBaseAdapter.new(
|
22
|
+
OpenBase.new(database, host, username, password), logger
|
23
|
+
)
|
24
|
+
|
25
|
+
if oba.raw_connection.connected?
|
26
|
+
unless oba.tables.include?(ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE)
|
27
|
+
oba.execute(<<-SQL,"Creating OpenBase Column Support Table")
|
28
|
+
CREATE TABLE #{ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE} (name char, type char, precision int, scale int)
|
29
|
+
SQL
|
30
|
+
end
|
31
|
+
oba.select_all("SELECT * FROM #{ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE}").each do |col|
|
32
|
+
ConnectionAdapters::OpenBaseAdapter::DECIMAL_COLUMNS.store(col["name"],[col["precision"],col["scale"]])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
oba
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
module ConnectionAdapters
|
42
|
+
class OpenBaseColumn < Column #:nodoc:
|
43
|
+
private
|
44
|
+
def simplified_type(field_type)
|
45
|
+
return :integer if field_type.downcase =~ /long/
|
46
|
+
return :decimal if field_type.downcase == "money"
|
47
|
+
return :binary if field_type.downcase == "object"
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
|
52
|
+
# http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
|
53
|
+
#
|
54
|
+
# Options:
|
55
|
+
#
|
56
|
+
# * <tt>:host</tt> -- Defaults to localhost
|
57
|
+
# * <tt>:username</tt> -- Defaults to nothing
|
58
|
+
# * <tt>:password</tt> -- Defaults to nothing
|
59
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
60
|
+
#
|
61
|
+
# The OpenBase adapter will make use of OpenBase's ability to generate unique ids
|
62
|
+
# for any column with an unique index applied. Thus, if the value of a primary
|
63
|
+
# key is not specified at the time an INSERT is performed, the adapter will prefetch
|
64
|
+
# a unique id for the primary key. This prefetching is also necessary in order
|
65
|
+
# to return the id after an insert.
|
66
|
+
#
|
67
|
+
# Caveat: Operations involving LIMIT and OFFSET do not yet work!
|
68
|
+
#
|
69
|
+
# Maintainer: derrick.spell@gmail.com
|
70
|
+
class OpenBaseAdapter < AbstractAdapter
|
71
|
+
DECIMAL_COLUMNS = {}
|
72
|
+
COLUMN_SUPPORT_TABLE = "rails_openbase_column_support"
|
73
|
+
def adapter_name
|
74
|
+
'OpenBase'
|
75
|
+
end
|
76
|
+
|
77
|
+
def native_database_types
|
78
|
+
{
|
79
|
+
:primary_key => "integer NOT NULL UNIQUE INDEX DEFAULT _rowid",
|
80
|
+
:string => { :name => "char", :limit => 4096 },
|
81
|
+
:text => { :name => "text" },
|
82
|
+
:integer => { :name => "integer" },
|
83
|
+
:float => { :name => "float" },
|
84
|
+
:decimal => { :name => "decimal" },
|
85
|
+
:datetime => { :name => "datetime" },
|
86
|
+
:timestamp => { :name => "timestamp" },
|
87
|
+
:time => { :name => "time" },
|
88
|
+
:date => { :name => "date" },
|
89
|
+
:binary => { :name => "object" },
|
90
|
+
:boolean => { :name => "boolean" }
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def supports_migrations?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def prefetch_primary_key?(table_name = nil)
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def default_sequence_name(table_name, primary_key) # :nodoc:
|
103
|
+
"#{table_name} #{primary_key}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def next_sequence_value(sequence_name)
|
107
|
+
ary = sequence_name.split(' ')
|
108
|
+
if (!ary[1]) then
|
109
|
+
ary[0] =~ /(\w+)_nonstd_seq/
|
110
|
+
ary[0] = $1
|
111
|
+
end
|
112
|
+
@connection.unique_row_id(ary[0], ary[1])
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# QUOTING ==================================================
|
117
|
+
|
118
|
+
def quote(value, column = nil)
|
119
|
+
if value.kind_of?(String) && column && column.type == :binary
|
120
|
+
"'#{@connection.insert_binary(value)}'"
|
121
|
+
elsif value.kind_of?(BigDecimal)
|
122
|
+
return "'#{value.to_s}'"
|
123
|
+
elsif column && column.type == :integer && column.sql_type =~ /decimal/
|
124
|
+
return "'#{value.to_s}'"
|
125
|
+
elsif [Float,Fixnum,Bignum].include?(value.class) && column && column.type == :string
|
126
|
+
return "'#{value.to_s}'"
|
127
|
+
else
|
128
|
+
super
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def quoted_true
|
133
|
+
"1"
|
134
|
+
end
|
135
|
+
|
136
|
+
def quoted_false
|
137
|
+
"0"
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
# DATABASE STATEMENTS ======================================
|
143
|
+
|
144
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
145
|
+
return if options[:limit].nil?
|
146
|
+
limit = options[:limit]
|
147
|
+
offset = options[:offset]
|
148
|
+
if limit == 0
|
149
|
+
# Mess with the where clause to ensure we get no results
|
150
|
+
if sql =~ /WHERE/i
|
151
|
+
sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
|
152
|
+
elsif sql =~ /ORDER\s+BY/i
|
153
|
+
sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
|
154
|
+
else
|
155
|
+
sql << 'WHERE 1 = 2'
|
156
|
+
end
|
157
|
+
elsif offset.nil?
|
158
|
+
sql << " RETURN RESULTS #{limit}"
|
159
|
+
else
|
160
|
+
sql << " RETURN RESULTS #{offset} TO #{limit + offset}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
165
|
+
execute(sql, name)
|
166
|
+
update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
|
167
|
+
id_value
|
168
|
+
end
|
169
|
+
|
170
|
+
def execute(sql, name = nil) #:nodoc:
|
171
|
+
log(sql, name) { @connection.execute(sql) }
|
172
|
+
end
|
173
|
+
|
174
|
+
def direct_execute(sql, name = nil) #:nodoc:
|
175
|
+
log(sql, name) { @connection.execute(sql) }
|
176
|
+
end
|
177
|
+
|
178
|
+
def update(sql, name = nil) #:nodoc:
|
179
|
+
execute(sql, name).rows_affected
|
180
|
+
end
|
181
|
+
|
182
|
+
alias_method :delete, :update #:nodoc:
|
183
|
+
|
184
|
+
def begin_db_transaction #:nodoc:
|
185
|
+
execute "START TRANSACTION"
|
186
|
+
rescue Exception
|
187
|
+
# Transactions aren't supported
|
188
|
+
end
|
189
|
+
|
190
|
+
def commit_db_transaction #:nodoc:
|
191
|
+
execute "COMMIT"
|
192
|
+
rescue Exception
|
193
|
+
# Transactions aren't supported
|
194
|
+
end
|
195
|
+
|
196
|
+
def rollback_db_transaction #:nodoc:
|
197
|
+
execute "ROLLBACK"
|
198
|
+
rescue Exception
|
199
|
+
# Transactions aren't supported
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
# SCHEMA STATEMENTS ========================================
|
204
|
+
# Return the list of all tables in the schema search path.
|
205
|
+
def tables(name = nil) #:nodoc:
|
206
|
+
tables = @connection.tables
|
207
|
+
tables.reject! { |t| /\A_SYS_/ === t }
|
208
|
+
end
|
209
|
+
|
210
|
+
def columns(table_name, name = nil) #:nodoc:
|
211
|
+
sql = "SELECT * FROM _sys_tables "
|
212
|
+
sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
|
213
|
+
sql << "ORDER BY columnNumber"
|
214
|
+
columns = []
|
215
|
+
direct_execute(sql, name).each_hash do |row|
|
216
|
+
columns << OpenBaseColumn.new(row["fieldname"],
|
217
|
+
default_value(row["defaultvalue"],row["typename"]),
|
218
|
+
sql_type_name(table_name,row["fieldname"],row["typename"],row["length"]),
|
219
|
+
(row["notnull"] == 1 ? false : true)
|
220
|
+
)
|
221
|
+
end
|
222
|
+
columns
|
223
|
+
end
|
224
|
+
|
225
|
+
def column_names(table_name) #:nodoc:
|
226
|
+
sql = "SELECT fieldname FROM _sys_tables "
|
227
|
+
sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
|
228
|
+
sql << "ORDER BY columnNumber"
|
229
|
+
names = direct_execute(sql).fetch_all
|
230
|
+
names.flatten! || names
|
231
|
+
end
|
232
|
+
|
233
|
+
def indexes(table_name, name = nil)#:nodoc:
|
234
|
+
sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
|
235
|
+
sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
|
236
|
+
sql << "AND primarykey=0 "
|
237
|
+
sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
|
238
|
+
sql << "ORDER BY columnNumber"
|
239
|
+
indexes = []
|
240
|
+
execute(sql, name).each do |row|
|
241
|
+
indexes << IndexDefinition.new(table_name,ob_index_name(row),row[3]==1,[row[0]])
|
242
|
+
end
|
243
|
+
indexes
|
244
|
+
end
|
245
|
+
|
246
|
+
def create_table(name, options = {}) #:nodoc:
|
247
|
+
return_value = super
|
248
|
+
|
249
|
+
# Get my own copy of TableDefinition so that i can detect decimal columns
|
250
|
+
table_definition = TableDefinition.new(self)
|
251
|
+
yield table_definition
|
252
|
+
|
253
|
+
table_definition.columns.each do |col|
|
254
|
+
if col.type == :decimal
|
255
|
+
record_decimal(name, col.name, col.precision, col.scale)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
unless options[:id] == false
|
260
|
+
primary_key = (options[:primary_key] || "id")
|
261
|
+
direct_execute("CREATE PRIMARY KEY #{name} (#{primary_key})")
|
262
|
+
end
|
263
|
+
return_value
|
264
|
+
end
|
265
|
+
|
266
|
+
def rename_table(name, new_name)
|
267
|
+
execute "RENAME #{name} #{new_name}"
|
268
|
+
end
|
269
|
+
|
270
|
+
def add_column(table_name, column_name, type, options = {})
|
271
|
+
return_value = super(table_name, "COLUMN " + column_name.to_s, type, options)
|
272
|
+
if type == :decimal
|
273
|
+
record_decimal(table_name, column_name, options[:precision], options[:scale])
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def remove_column(table_name, column_name)
|
278
|
+
execute "ALTER TABLE #{table_name} REMOVE COLUMN #{quote_column_name(column_name)}"
|
279
|
+
end
|
280
|
+
|
281
|
+
def rename_column(table_name, column_name, new_column_name)
|
282
|
+
execute "ALTER TABLE #{table_name} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
283
|
+
end
|
284
|
+
|
285
|
+
def add_column_options!(sql, options) #:nodoc:
|
286
|
+
sql << " NOT NULL" if options[:null] == false
|
287
|
+
sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
|
288
|
+
end
|
289
|
+
|
290
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
291
|
+
unless options_include_default?(options)
|
292
|
+
options[:default] = select_one("SELECT * FROM _sys_tables WHERE tablename='#{table_name}' AND fieldname='#{column_name}'")["defaultvalue"]
|
293
|
+
end
|
294
|
+
|
295
|
+
change_column_sql = "ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
296
|
+
add_column_options!(change_column_sql, options)
|
297
|
+
execute(change_column_sql)
|
298
|
+
end
|
299
|
+
|
300
|
+
def change_column_default(table_name, column_name, default)
|
301
|
+
execute "ALTER TABLE #{table_name} COLUMN #{column_name} SET DEFAULT #{quote(default)}"
|
302
|
+
end
|
303
|
+
|
304
|
+
def add_index(table_name, column_name, options = {})
|
305
|
+
if Hash === options # legacy support, since this param was a string
|
306
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
307
|
+
else
|
308
|
+
index_type = options
|
309
|
+
end
|
310
|
+
execute "CREATE #{index_type} INDEX #{table_name} #{column_name}"
|
311
|
+
end
|
312
|
+
|
313
|
+
def remove_index(table_name, options = {})
|
314
|
+
execute "DROP INDEX #{table_name} #{options === Hash ? options[:column] : options}"
|
315
|
+
end
|
316
|
+
|
317
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
318
|
+
return super unless type.to_s == 'decimal'
|
319
|
+
|
320
|
+
if (scale.to_i == 2)
|
321
|
+
return 'money'
|
322
|
+
elsif (scale.to_i == 0)
|
323
|
+
return 'longlong'
|
324
|
+
else
|
325
|
+
return "char(#{precision.to_i + 1})"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
private
|
332
|
+
def select(sql, name = nil)
|
333
|
+
decimals = detect_decimals(sql) || []
|
334
|
+
sql = add_order_by_rowid(sql)
|
335
|
+
|
336
|
+
# OpenBase ignores the return results when there is a group by
|
337
|
+
# so limit the result set that we return to rails if need be
|
338
|
+
if (sql =~ /GROUP BY/i)
|
339
|
+
sql.sub!(/RETURN RESULTS (\d+)( TO (\d+))?/i,"")
|
340
|
+
|
341
|
+
results = execute(sql, name)
|
342
|
+
if ($2)
|
343
|
+
results.fetch_offset = $1.to_i
|
344
|
+
results.fetch_limit = $3.to_i - $1.to_i
|
345
|
+
elsif ($1)
|
346
|
+
results.fetch_limit = $1.to_i
|
347
|
+
end
|
348
|
+
else
|
349
|
+
results = execute(sql, name)
|
350
|
+
end
|
351
|
+
|
352
|
+
rows = []
|
353
|
+
if ( results.rows_affected )
|
354
|
+
results.each_hash do |row| # loop through result rows
|
355
|
+
# row.delete("_rowid") if row.key?("_rowid")
|
356
|
+
decimals.each do |name, precision, scale|
|
357
|
+
row[name] = BigDecimal.new(row[name]) if row[name] === String
|
358
|
+
end
|
359
|
+
rows << row
|
360
|
+
end
|
361
|
+
end
|
362
|
+
rows
|
363
|
+
end
|
364
|
+
|
365
|
+
def default_value(value,type=nil)
|
366
|
+
return value if value.nil?
|
367
|
+
|
368
|
+
# Boolean type values
|
369
|
+
return true if value =~ /true/
|
370
|
+
return false if value =~ /false/
|
371
|
+
# Alternative boolean default declarations
|
372
|
+
return true if (value == 1 && type == "boolean")
|
373
|
+
return false if (value == 0 && type == "boolean")
|
374
|
+
|
375
|
+
# Date / Time magic values
|
376
|
+
return Time.now.to_s if value =~ /^now\(\)/i
|
377
|
+
|
378
|
+
# Empty strings should be set to nil
|
379
|
+
return nil if value.empty?
|
380
|
+
|
381
|
+
# Otherwise return what we got from OpenBase
|
382
|
+
# and hope for the best...
|
383
|
+
# Take off the leading space and unquote
|
384
|
+
value.lstrip!
|
385
|
+
value = value[1,value.length-2] if value.first.eql?("'") && value.last.eql?("'")
|
386
|
+
return nil if value.eql?("NULL")
|
387
|
+
return value
|
388
|
+
end
|
389
|
+
|
390
|
+
def sql_type_name(table_name, col_name, type, length)
|
391
|
+
full_name = table_name.to_s + "." + col_name.to_s
|
392
|
+
if DECIMAL_COLUMNS.include?(full_name) && type != "longlong"
|
393
|
+
return "decimal(#{DECIMAL_COLUMNS[full_name][0]},#{DECIMAL_COLUMNS[full_name][1]})"
|
394
|
+
end
|
395
|
+
return "#{type}(#{length})" if ( type =~ /char/ )
|
396
|
+
type
|
397
|
+
end
|
398
|
+
|
399
|
+
def ob_index_name(row = [])
|
400
|
+
name = ""
|
401
|
+
name << "UNIQUE " if row[3]
|
402
|
+
name << "CLUSTERED " if row[4]
|
403
|
+
name << "INDEX"
|
404
|
+
name
|
405
|
+
end
|
406
|
+
|
407
|
+
def detect_decimals(sql)
|
408
|
+
#=begin
|
409
|
+
# Detect any decimal columns that will need to be cast when fetched
|
410
|
+
decimals = []
|
411
|
+
sql =~ /SELECT\s+(.*)\s+FROM\s+(\w+)/i
|
412
|
+
select_clause = $1
|
413
|
+
main_table = $2
|
414
|
+
if select_clause == "*"
|
415
|
+
column_names(main_table).each do |col|
|
416
|
+
full_name = main_table + "." + col
|
417
|
+
if DECIMAL_COLUMNS.include?(full_name)
|
418
|
+
decimals << [col,DECIMAL_COLUMNS[full_name][0].to_i,DECIMAL_COLUMNS[full_name][1].to_i]
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
return decimals
|
423
|
+
=begin {
|
424
|
+
# Change table.* to list of columns in table
|
425
|
+
while (sql =~ /SELECT.*\s(\w+)\.\*/)
|
426
|
+
table = $1
|
427
|
+
cols = columns(table)
|
428
|
+
if ( cols.size == 0 ) then
|
429
|
+
# Maybe this is a table alias
|
430
|
+
sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
|
431
|
+
$1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
|
432
|
+
cols = columns($1)
|
433
|
+
end
|
434
|
+
select_columns = []
|
435
|
+
cols.each do |col|
|
436
|
+
select_columns << table + '.' + col.name
|
437
|
+
end
|
438
|
+
sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
|
439
|
+
end
|
440
|
+
|
441
|
+
# Change JOIN clause to table list and WHERE condition
|
442
|
+
}
|
443
|
+
=begin {
|
444
|
+
while (sql =~ /JOIN/)
|
445
|
+
sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
|
446
|
+
join_clause = $1 + $5
|
447
|
+
is_outer_join = $3
|
448
|
+
join_table = $4
|
449
|
+
join_condition = $5
|
450
|
+
join_condition.gsub!(/=/,"*") if is_outer_join
|
451
|
+
if (sql =~ /WHERE/)
|
452
|
+
sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
|
453
|
+
else
|
454
|
+
sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
|
455
|
+
end
|
456
|
+
sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
|
457
|
+
from_clause = $1
|
458
|
+
sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
|
459
|
+
sql.gsub!(join_clause,"")
|
460
|
+
end
|
461
|
+
|
462
|
+
|
463
|
+
# ORDER BY _rowid if no explicit ORDER BY
|
464
|
+
# This will ensure that find(:first) returns the first inserted row
|
465
|
+
if (sql !~ /(ORDER BY)|(GROUP BY)/)
|
466
|
+
if (sql =~ /RETURN RESULTS/)
|
467
|
+
sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
|
468
|
+
else
|
469
|
+
sql << " ORDER BY _rowid"
|
470
|
+
end
|
471
|
+
end
|
472
|
+
}
|
473
|
+
=end
|
474
|
+
end
|
475
|
+
|
476
|
+
def add_order_by_rowid(sql)
|
477
|
+
# ORDER BY _rowid if no explicit ORDER BY
|
478
|
+
# This will ensure that find(:first) returns the first inserted row
|
479
|
+
if (sql !~ /(ORDER BY)|(GROUP BY)/)
|
480
|
+
if (sql =~ /RETURN RESULTS/)
|
481
|
+
sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
|
482
|
+
else
|
483
|
+
sql << " ORDER BY _rowid"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
sql
|
487
|
+
end
|
488
|
+
|
489
|
+
def record_decimal(table_name, column_name, precision, scale)
|
490
|
+
full_name = table_name.to_s + "." + column_name.to_s
|
491
|
+
DECIMAL_COLUMNS.store(full_name, [precision.to_i,scale.to_i])
|
492
|
+
direct_execute("INSERT INTO #{COLUMN_SUPPORT_TABLE} (name,type,precision,scale) VALUES ('#{full_name}','decimal',#{precision.to_i},#{scale.to_i})")
|
493
|
+
end
|
494
|
+
|
495
|
+
def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
496
|
+
sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
|
497
|
+
table = $1
|
498
|
+
cols = $2
|
499
|
+
values = $3
|
500
|
+
cols = cols.split(',')
|
501
|
+
values.gsub!(/'[^']*'/,"''")
|
502
|
+
values.gsub!(/"[^"]*"/,"\"\"")
|
503
|
+
values = values.split(',')
|
504
|
+
update_cols = []
|
505
|
+
values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
|
506
|
+
update_sql = "UPDATE #{table} SET"
|
507
|
+
update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
|
508
|
+
update_sql.chop!()
|
509
|
+
update_sql << " WHERE #{pk}=#{quote(id_value)}"
|
510
|
+
direct_execute(update_sql,"Null Correction") if update_cols.size > 0
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
data/test/test_helper.rb
ADDED
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: DimaD-daylite-models
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dmitriy Dzema
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-11 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: activerecord
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 2.0.0
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activerecord
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.3
|
41
|
+
version:
|
42
|
+
description:
|
43
|
+
email: dimad.ag@gmail.com
|
44
|
+
executables: []
|
45
|
+
|
46
|
+
extensions: []
|
47
|
+
|
48
|
+
extra_rdoc_files:
|
49
|
+
- History.txt
|
50
|
+
- Manifest.txt
|
51
|
+
files:
|
52
|
+
- History.txt
|
53
|
+
- LICENSE
|
54
|
+
- Manifest.txt
|
55
|
+
- README.markdown
|
56
|
+
- Rakefile
|
57
|
+
- app/models/daylite.rb
|
58
|
+
- app/models/daylite/contact.rb
|
59
|
+
- app/models/daylite/organization.rb
|
60
|
+
- app/models/daylite/organization_task_join.rb
|
61
|
+
- app/models/daylite/project.rb
|
62
|
+
- app/models/daylite/project_task_join.rb
|
63
|
+
- app/models/daylite/role.rb
|
64
|
+
- app/models/daylite/role_type.rb
|
65
|
+
- app/models/daylite/task.rb
|
66
|
+
- app/models/daylite/user.rb
|
67
|
+
- config/database.yml.exmp
|
68
|
+
- daylite_models.gemspec
|
69
|
+
- daylite_models.rb
|
70
|
+
- init.rb
|
71
|
+
- install.rb
|
72
|
+
- lib/active_record/connection_adapters/openbase_adapter.rb
|
73
|
+
- tasks/users_plugin_tasks.rake
|
74
|
+
- test/daylite_models_plugin_test.rb
|
75
|
+
- test/test_helper.rb
|
76
|
+
- uninstall.rb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage:
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options:
|
81
|
+
- --main
|
82
|
+
- README.markdown
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
version:
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
version:
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project: daylite-models
|
100
|
+
rubygems_version: 1.2.0
|
101
|
+
signing_key:
|
102
|
+
specification_version: 2
|
103
|
+
summary: Set of ActiveRecord classes to work with the Daylite 3 database
|
104
|
+
test_files:
|
105
|
+
- test/test_helper.rb
|