lca 0.2.4 → 0.2.9

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
  SHA256:
3
- metadata.gz: fc53882ad2b8c5c34b869be0546546973dd829b25aa39f354e9be9da63a44fae
4
- data.tar.gz: 57c65eeadf05e7e6f160ea1c2ca3db8a025614578ecb6242d9f1404002651766
3
+ metadata.gz: ae08a160b647985c85ef3181992f091ac978411c2409bc23de69daf97a95e1a3
4
+ data.tar.gz: 735cbda1c8bbde69c6b015d5d09f97fee07d7830de21978c40086910810e7d1f
5
5
  SHA512:
6
- metadata.gz: 2a1542ddf863b9461fb3ceb67e45f59599608c40e98994203918b7aeb4fb87ef2e4b1f73bd7fa3a837a0cd9cbd691cf72648505f38f156c64b84b6ee097d0dd6
7
- data.tar.gz: e7989620c9b0a81249e741b41bcbe8fd1854b24650d66e4963ce940ab593d8ea2340403f06f467d8e280cea3dfed99d1c5067407771557403bf2d049cf792b0c
6
+ metadata.gz: 794b1b5deb3ed8ed9e44d0bf0252ddb7144e9dab733ec0ae6c28bb39d933317b1c28463c74d4a556dda9d91fbcc77ae1466a020bda7ba09aa15eb96a3336a451
7
+ data.tar.gz: b7aa19cf60dfe5c510b206a9e00f4afbaee46168fe1e82860d3c82b39e966dc02613e4a49c9a2f2356a19c432e01eedd99ed82fa054ec5c771bc1402426a3725
data/Gemfile CHANGED
@@ -8,8 +8,6 @@ gem "rake", "~> 13.0"
8
8
  gem "rspec", "~> 3.0"
9
9
 
10
10
  gem "activerecord", "~> 6.0"
11
- gem "activestorage", "~> 6.0"
12
- gem "actiontext", "~> 6.0"
13
-
11
+ gem "active_tree", "~> 0.2.8"
14
12
  gem "jwt"
15
13
  gem "pg_ltree"
data/Gemfile.lock CHANGED
@@ -1,87 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lca (0.1.18)
5
- actiontext (~> 6.0)
4
+ lca (0.2.8)
5
+ active_tree (~> 0.2.8)
6
6
  activerecord (~> 6.0)
7
- activestorage (~> 6.0)
8
7
  jwt (~> 2.2.3)
9
8
  pg_ltree (~> 1.1.8)
10
9
 
11
10
  GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
- actionpack (6.1.4.1)
15
- actionview (= 6.1.4.1)
16
- activesupport (= 6.1.4.1)
17
- rack (~> 2.0, >= 2.0.9)
18
- rack-test (>= 0.6.3)
19
- rails-dom-testing (~> 2.0)
20
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
21
- actiontext (6.1.4.1)
22
- actionpack (= 6.1.4.1)
23
- activerecord (= 6.1.4.1)
24
- activestorage (= 6.1.4.1)
25
- activesupport (= 6.1.4.1)
26
- nokogiri (>= 1.8.5)
27
- actionview (6.1.4.1)
28
- activesupport (= 6.1.4.1)
29
- builder (~> 3.1)
30
- erubi (~> 1.4)
31
- rails-dom-testing (~> 2.0)
32
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
33
- activejob (6.1.4.1)
34
- activesupport (= 6.1.4.1)
35
- globalid (>= 0.3.6)
13
+ active_tree (0.2.9)
14
+ activerecord (~> 6.0)
15
+ jwt (~> 2.2.3)
16
+ pg_ltree (~> 1.1.8)
36
17
  activemodel (6.1.4.1)
37
18
  activesupport (= 6.1.4.1)
38
19
  activerecord (6.1.4.1)
39
20
  activemodel (= 6.1.4.1)
40
21
  activesupport (= 6.1.4.1)
41
- activestorage (6.1.4.1)
42
- actionpack (= 6.1.4.1)
43
- activejob (= 6.1.4.1)
44
- activerecord (= 6.1.4.1)
45
- activesupport (= 6.1.4.1)
46
- marcel (~> 1.0.0)
47
- mini_mime (>= 1.1.0)
48
22
  activesupport (6.1.4.1)
49
23
  concurrent-ruby (~> 1.0, >= 1.0.2)
50
24
  i18n (>= 1.6, < 2)
51
25
  minitest (>= 5.1)
52
26
  tzinfo (~> 2.0)
53
27
  zeitwerk (~> 2.3)
54
- builder (3.2.4)
55
28
  concurrent-ruby (1.1.9)
56
- crass (1.0.6)
57
29
  diff-lcs (1.4.4)
58
- erubi (1.10.0)
59
- globalid (0.5.2)
60
- activesupport (>= 5.0)
61
30
  i18n (1.8.10)
62
31
  concurrent-ruby (~> 1.0)
63
32
  jwt (2.2.3)
64
- loofah (2.12.0)
65
- crass (~> 1.0.2)
66
- nokogiri (>= 1.5.9)
67
- marcel (1.0.1)
68
- mini_mime (1.1.1)
69
33
  minitest (5.14.4)
70
- nokogiri (1.12.4-x86_64-linux)
71
- racc (~> 1.4)
72
34
  pg (1.2.3)
73
35
  pg_ltree (1.1.8)
74
36
  activerecord (>= 4.0.0, <= 7.0.0.rc1)
75
37
  pg (>= 0.17.0, < 2)
76
- racc (1.5.2)
77
- rack (2.2.3)
78
- rack-test (1.1.0)
79
- rack (>= 1.0, < 3)
80
- rails-dom-testing (2.0.3)
81
- activesupport (>= 4.2.0)
82
- nokogiri (>= 1.6)
83
- rails-html-sanitizer (1.4.2)
84
- loofah (~> 2.3)
85
38
  rake (13.0.6)
86
39
  rspec (3.10.0)
87
40
  rspec-core (~> 3.10.0)
@@ -104,10 +57,11 @@ PLATFORMS
104
57
  x86_64-linux
105
58
 
106
59
  DEPENDENCIES
107
- actiontext (~> 6.0)
60
+ active_tree (~> 0.2.8)
108
61
  activerecord (~> 6.0)
109
- activestorage (~> 6.0)
62
+ jwt
110
63
  lca!
64
+ pg_ltree
111
65
  rake (~> 13.0)
112
66
  rspec (~> 3.0)
113
67
 
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
- [![Gem Version](https://badge.fury.io/rb/lca.svg)](https://badge.fury.io/rb/lca)
1
+ [![Tests](https://github.com/nicksterious/lca/actions/workflows/ci.yml/badge.svg)](https://github.com/nicksterious/lca/actions/workflows/ci.yml) [![Ruby Gem](https://github.com/nicksterious/lca/actions/workflows/rubygems.yml/badge.svg)](https://github.com/nicksterious/lca/actions/workflows/rubygems.yml) [![Gem Version](https://badge.fury.io/rb/lca.svg)](https://badge.fury.io/rb/lca)
2
2
 
3
3
  # LCA
4
4
 
5
5
  Storing, processing and working with life-cycle assessment data has always been challenging. A multitude of data models and implementations exist already but every one of them makes huge compromises or lacks functionality.
6
6
 
7
- This gem implements a denormalized database model for life-cycle assessment data as well as several innovative query interfaces.
7
+ This gem implements a denormalized database model for life-cycle assessment data as well as several vectors for convenient querying.
8
8
 
9
9
  ## Installation
10
10
 
11
+ This gem is at home within a Rails 6+ console-only, API or full fledged application on top of a Postgres database.
12
+
11
13
  Add this line to your application's Gemfile:
12
14
 
13
15
  ```ruby
@@ -22,15 +24,15 @@ Or install it yourself as:
22
24
 
23
25
  $ gem install lca
24
26
 
25
- Upon installing the gem you must run the install process in your Rails application's root:
27
+ Upon installing the gem you must run the ActiveTree install process in your Rails application's root:
26
28
 
27
- $ rails g lca:install
29
+ $ rails g active_tree:install
28
30
 
29
- This will generate a `config/lca.yml` which you may customize to your needs, an initializer and a migration file which you can also customize to add any database columns your models will require.
31
+ This will generate a `config/active_tree.yml` which you may customize to your needs, an initializer and a migration file which you can also customize to add any database columns your models will require.
30
32
 
31
33
  ## Usage
32
34
 
33
- Include the LCA concern into one of your models which will own lifecycle trees (owner model):
35
+ Include the ActiveTree concern into one of your models which will own lifecycle trees (owner model):
34
36
 
35
37
  ```ruby
36
38
  class User < ApplicationRecord
@@ -42,58 +44,69 @@ Include the LCA concern into one of your models which will own lifecycle trees (
42
44
  This will extend your model and enable the following functionality:
43
45
 
44
46
  ```ruby
45
- # query with ActiveRecord syntax
46
- User.last.lca_cycles
47
- User.find_by(name: "Acme").lca_cycles.impacts.select("impact_unit, sum(impact_amount) as total_impact").group(:impact_unit)
48
- User.last.lca_cycles.where(...)
49
- User.last.lca_cycles.where(...).group(...)
50
- User.last.lca_cycles.where(...).limit(...).offset(...)
47
+ # query with ActiveRecord syntax
48
+ User.last.active_trees
49
+ User.find_by(name: "Acme").lca_cycles.impacts.select("impact_unit, sum(impact_amount) as total_impact").group(:impact_unit)
50
+ User.last.active_trees.where(...)
51
+ User.last.active_trees.where(...).group(...)
52
+ User.last.active_trees.where(...).limit(...).offset(...)
51
53
 
52
- # AR query with ltree syntax
53
- User.last.lca_cycles.disabled.match_path("*.CustomProcess.*")
54
- User.last.lca_cycles.match_path("*{5,10}.CustomProcess.*.Ecosphere.*")
55
- User.last.lca_cycles.active.match_path("*.WhateverProcess.*.Ecosphere.*.CO2Emission.*").where( impact_amount: [100..150]).sum(:impact_amount)
56
-
57
- Lca::Cycle.match_path("Top.Electric Vehicle.*").impacts.match_path("*.CO2*").where( impact_amount: [ 1000..10000 ]).average(:impact_amount)
58
- Lca::Cycle::Product.where(name: "Electric Vehicle Battery").impacts.match_path("*.Cobalt.*").sum(:impact_amount)
59
- Lca::Process.where(owner: User.last).match_path("*{10,20}.*Assembly, automated.*")
60
- Lca::Impact.match_path("*.Transport by truck.*")
61
- Lca::Exchange.match_path("*.Oil.*.Ecosphere.*").impacts.sum(:impact_amount)
62
- Lca::Product.match_path("*.ElectricVehicle.*").processes.match_path("*.Processing.*").where(location: "EU").impacts.match_path("*.Lithium.*").where(location: ["CN", "Africa"]).sum(:impact_amount)
54
+ # AR query with ltree syntax
55
+ User.last.active_trees.disabled.match_path("*.CustomProcess.*")
56
+ User.last.active_trees.match_path("*{5,10}.CustomProcess.*.Ecosphere.*")
57
+ User.last.active_trees.active.match_path("*.WhateverProcess.*.Ecosphere.*.CO2Emission.*").where( impact_amount: [100..150]).sum(:impact_amount)
58
+
59
+ Lca::Cycle.match_path("Top.Electric Vehicle.*").impacts.match_path("*.CO2*").where( impact_amount: [ 1000..10000 ]).average(:impact_amount)
60
+ Lca::Cycle::Product.where(name: "Electric Vehicle Battery").impacts.match_path("*.Cobalt.*").sum(:impact_amount)
61
+ Lca::Process.where(owner: User.last).match_path("*{10,20}.*Assembly, automated.*")
62
+ Lca::Impact.match_path("*.Transport by truck.*")
63
+ Lca::Exchange.match_path("*.Oil.*.Ecosphere.*").impacts.sum(:impact_amount)
64
+ Lca::Product.match_path("*.ElectricVehicle.*").processes.match_path("*.Processing.*").where(location: "EU").impacts.match_path("*.Lithium.*").where(location: ["CN", "Africa"]).sum(:impact_amount)
63
65
 
64
- # pg_ltree queries
65
- User.last.lca_cycles.last.parent
66
- Lca::Process.match_path("*.Manual assembly.*").children
66
+ # pg_ltree queries
67
+ User.last.active_trees.last.parent
68
+ Lca::Process.match_path("*.Manual assembly.*").children
67
69
 
68
- # pg_ltree combined with AR syntax
69
- User.last.lca_cycles(type: "Lca::Product").children.match_path("*.Retail").children.exchanges
70
+ # pg_ltree combined with AR syntax
71
+ User.last.active_trees(type: "Lca::Product").children.match_path("*.Retail").children.exchanges
70
72
 
71
- # all queries can be directed to a specific partition:
72
- Lca::Process.owned_by( owner_id ).match_path("*.Recycling.*").where(impact_unit: "tons CO2/year").impacts.sum(:impact_amount)
73
+ # all queries can be directed to a specific partition:
74
+ Lca::Process.owned_by( owner_id ).match_path("*.Recycling.*").where(impact_unit: "tons CO2/year").impacts.sum(:impact_amount)
73
75
 
74
76
  ```
75
77
 
76
78
  The gem also creates some default models:
77
79
 
78
80
  ```ruby
79
- Lca::Process::Transport::ByAir.match_path("*.CO2Emission.*").impacts.sum(:impact_amount)
80
- Lca::Impact::Ecosphere::Fauna.match_path("*.ResourceExtraction.*").where(impact_unit: "Species killed/year").sum(:impact_amount)
81
+ Lca::Process::Transport::ByAir.match_path("*.CO2Emission.*").impacts.sum(:impact_amount)
82
+ Lca::Impact::Ecosphere::Fauna.match_path("*.ResourceExtraction.*").where(impact_unit: "Species killed/year").sum(:impact_amount)
81
83
  ```
82
84
 
83
85
  To see what syntax to use for path traversal please check out the following resources:
86
+ * active_tree gem https://github.com/nicksterious/active_tree
84
87
  * pg_ltree gem https://github.com/sjke/pg_ltree
85
88
  * Postgres ltree extension documentation https://www.postgresql.org/docs/9.1/ltree.html
86
89
 
87
- The LCA gem is designed to be compatible with PostgREST. PostgREST is an amazing tool that generates a CRUD REST API for your Postgres database, read more about it here: https://postgrest.org
90
+ ## Caveats
91
+
92
+ `pg_ltree` .child / .parent queries do not work across different models due to an ActiveRecord limitation that requires results to be related via inheritance.
93
+
94
+ The following behaviors have been observed:
95
+
96
+ ```ruby
88
97
 
89
- If the `create_postgrest_roles` setting is on each new owner will be assigned a Postgres role allowing them to access data within their partition using PostgREST. Your owner model will be extended with a `.generate_jwt` method you can use to generate the PostgREST authentication token.
98
+ Lca::Process.last.children.impacts.where(impact_amount: 50)
99
+ # => nil
100
+
101
+ Lca::Process.last.children.unscope(where: :type).impacts.where(impact_amount: 50)
102
+ # => ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: Lca::Impact is not a subclass of Lca::Process)
103
+ ```
90
104
 
91
105
  ## Roadmap
92
106
 
93
107
  * .child / .parent query support across models unrelated through inheritance
94
108
  * more model templates for various LCA cycles, processes and impacts
95
109
  * model validations
96
- * query objects
97
110
  * postgres RECURSIVE queries
98
111
  * builders
99
112
  * seeds/fixtures
@@ -103,10 +116,6 @@ If the `create_postgrest_roles` setting is on each new owner will be assigned a
103
116
 
104
117
  Bug reports, pull requests and feature suggestions are welcome on GitHub at https://github.com/nicksterious/lca
105
118
 
106
- ## Sponsors
107
-
108
- We welcome any sponsorship through Github Sponsors. All corporate sponsors' logo and URL of choice shall show up within this section.
109
-
110
119
  ## License
111
120
 
112
121
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -10,17 +10,6 @@ module Lca
10
10
  migration_template "migration.rb.tt", "db/migrate/install_lca.rb", migration_version: migration_version
11
11
  end
12
12
 
13
- def copy_initializer
14
- copy_file 'initializer.rb.tt', 'config/initializers/lca.rb'
15
- end
16
-
17
- def copy_config
18
- conf_file = "config/lca.yml"
19
- copy_file "config.yml.tt", conf_file
20
- contents = File.read( conf_file ).gsub("changeme", ('a'..'z').to_a.shuffle.first(4).join )
21
- File.open(conf_file, 'wb') { |file| file.write(contents) }
22
- end
23
-
24
13
  def migration_version
25
14
  "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
26
15
  end
@@ -3,59 +3,48 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
3
3
 
4
4
  # enable ltree extension
5
5
  begin
6
- execute "create extension ltree"
6
+ execute "create extension ltree"
7
7
  rescue
8
- p "LTREE was already enabled"
8
+ p "LTREE was already enabled"
9
9
  end
10
10
 
11
- # LCA_OPTIONS[:table_name]
11
+ # add any columns required by your business model
12
+ add_column ACTIVE_TREE_OPTIONS[:table_name], :impact_amount, :decimal
13
+ add_column ACTIVE_TREE_OPTIONS[:table_name], :impact_amount_previous, :decimal
14
+ add_column ACTIVE_TREE_OPTIONS[:table_name], :impact_amount_unit, :text
15
+ add_column ACTIVE_TREE_OPTIONS[:table_name], :impact_factor, :decimal
16
+ add_column ACTIVE_TREE_OPTIONS[:table_name], :impact_precision, :decimal
17
+
18
+
19
+ # snapshots table
20
+ # maybe this should only store the impacts?
12
21
  execute <<-SQL
13
- create table #{ LCA_OPTIONS[:table_name] } (
22
+ create table #{ ACTIVE_TREE_OPTIONS[:table_name] }_snapshots (
14
23
  id serial,
15
24
 
25
+ impact_id integer,
26
+
16
27
  owner_id integer,
17
- owner_type text,
18
-
19
- data_external_id text,
20
- data_provider text,
21
-
22
- type text,
23
- name text,
24
-
25
- parent_entity_id integer,
26
- parent_entity_type text,
27
-
28
- path ltree,
29
- path_slug text,
28
+ owner_type character varying,
30
29
 
31
30
  impact_amount decimal,
31
+ impact_amount_previous decimal,
32
32
  impact_amount_unit text,
33
33
  impact_factor decimal,
34
34
  impact_precision decimal,
35
35
 
36
+ created_at timestamp(6) without time zone not null,
37
+ updated_at timestamp(6) without time zone not null,
38
+
36
39
  primary key (id, owner_id)
37
40
  ) partition by list(owner_id)
38
41
  SQL
39
42
 
40
- # TODO do we want to also create an "others" partition?
41
- # "CREATE TABLE #{LCA_OPTIONS[:table_name]}_others PARTITION OF #{LCA_OPTIONS[:table_name]} DEFAULT"
42
-
43
- # add indexes
44
- add_index LCA_OPTIONS[:table_name], :id
45
- add_index LCA_OPTIONS[:table_name], :owner_id
46
- add_index LCA_OPTIONS[:table_name], :type
47
- add_index LCA_OPTIONS[:table_name], :parent_entity_id
48
-
49
- # next two indexes unfortunately can't be unique since a cycle can appear several times under an owner
50
- add_index LCA_OPTIONS[:table_name], :path, using: :gist
51
- add_index LCA_OPTIONS[:table_name], [:data_provider, :data_external_id]
52
-
53
- if LCA_OPTIONS[:create_postgrest_roles]
54
- # create postgrest anon user with no privs
55
- # postgrest may pass an user's role using JWT
56
- execute "drop role if exists postgrest_anon"
57
- execute "create role postgrest_anon nologin"
58
- execute "grant postgrest_anon to #{ LCA_OPTIONS[:database_user] }"
59
- end
43
+ # index the snapshots table
44
+ add_index "#{ACTIVE_TREE_OPTIONS[:table_name]}_snapshots", :id
45
+ add_index "#{ACTIVE_TREE_OPTIONS[:table_name]}_snapshots", :owner_id
46
+ add_index "#{ACTIVE_TREE_OPTIONS[:table_name]}_snapshots", :impact_id
47
+ add_index "#{ACTIVE_TREE_OPTIONS[:table_name]}_snapshots", :created_at
48
+
60
49
  end
61
50
  end
@@ -1,6 +1,6 @@
1
- # this still needed?
2
1
  # we include the concern manually so there should be no need for this anymore
3
- ActiveSupport.on_load(:active_record) do
4
- puts "tf is AR still being extended?"
5
- extend Lca::Lcable
6
- end
2
+ # besides, no need to polute other classes with our stuff.
3
+
4
+ # ActiveSupport.on_load(:active_record) do
5
+ # extend Lca::Lcable
6
+ # end
@@ -1,84 +1,60 @@
1
1
  require "jwt"
2
2
 
3
+ # read https://www.postgresqltutorial.com/postgresql-schema/
4
+
3
5
  module Lca
4
6
  module Lcable
5
7
  extend ActiveSupport::Concern
6
8
 
7
- # def self.included(base)
8
- # ::Lca::Model.class_exec do
9
- # @@owner_class = base.name
10
- # end
11
- # end
12
-
13
9
  included do
10
+ before_destroy :lca_delete_storage
14
11
 
15
- has_many :lca_cycles, class_name: "::Lca::Model", foreign_key: :owner_id, as: :owner
16
- after_create :lca_create_storage
17
- before_destroy :lca_delete_storage
18
-
19
- # instance methods
20
- def lca_role
21
- "lca_owner_#{id}_#{ LCA_OPTIONS[:owner_role_suffix] }"
22
- end # role
23
-
24
- # Generates a JWT token the client (SPA) can pass to PostgREST for privilege escalation
25
- def generate_jwt
26
- payload = { role: self.lca_role }
27
- ::JWT.encode payload, LCA_OPTIONS[:jwt_secret], LCA_OPTIONS[:jwt_encryption]
28
- end # .generate_jwt
29
-
30
-
31
- # Returns the LCA table name as configured within config/lca.yml
32
- def lca_table_name
33
- LCA_OPTIONS[:table_name]
34
- end # .lca_table_name
35
-
36
-
37
- # Creates LCA table partition and role for owner
38
- def lca_create_storage
12
+ #
13
+ include ActiveTree::ActiveTreeAble
14
+ after_create :lca_create_storage
39
15
 
40
- # create data partition
41
- lca_sql "create table if not exists #{lca_table_name}_#{id} partition of #{lca_table_name} for values in (#{id})"
42
- # TODO create partition indexes
16
+ has_many :lca_cycles, class_name: "::Lca::Model", foreign_key: :owner_id, as: :owner
43
17
 
44
- if LCA_OPTIONS[:create_postgrest_roles]
45
- # drop role if it exists
46
- lca_sql "drop role if exists #{ lca_role }"
47
18
 
48
- # create role
49
- lca_sql "create role #{ lca_role }"
19
+ def lca_role
20
+ "active_tree_owner_#{id}_#{ ACTIVE_TREE_OPTIONS[:owner_role_suffix] }"
21
+ end # role
50
22
 
51
- # grant privs
52
- lca_sql "grant all privileges on #{lca_table_name}_#{id} to #{ lca_role }"
53
- end
54
23
 
55
- end # create_storage
24
+ def lca_table_name
25
+ ACTIVE_TREE_OPTIONS[:table_name]
26
+ end
56
27
 
57
28
 
58
- # Deletes or detaches the partition and removes the role for this owner
59
- def lca_delete_storage
29
+ def lca_create_storage
30
+ # create snapshots partition
31
+ lca_sql "create table if not exists #{lca_table_name}_snapshots_#{id} partition of #{lca_table_name}_snapshots for values in (#{id})"
32
+
33
+ # if roles are enabled, grant privileges
34
+ if ACTIVE_TREE_OPTIONS[:create_postgrest_roles]
35
+ lca_sql "grant all privileges on #{lca_table_name}_snapshots_#{id} to #{ lca_role }"
36
+ end
37
+ end
60
38
 
61
- if LCA_OPTIONS[:create_postgrest_roles]
62
- # revoke privs
63
- lca_sql "REVOKE ALL PRIVILEGES ON #{lca_table_name}_#{id} FROM #{ lca_role }"
64
39
 
65
- # delete role
66
- lca_sql "drop role #{ lca_role }"
67
- end
40
+ def lca_delete_storage
41
+ # remove privs
42
+ if ACTIVE_TREE_OPTIONS[:create_postgrest_roles]
43
+ lca_sql "REVOKE ALL PRIVILEGES ON #{lca_table_name}_snapshots_#{id} FROM #{ lca_role }"
44
+ end
68
45
 
69
- if LCA_OPTIONS[:destroy_partition_on_owner_destroy]
70
- # delete partition
71
- lca_sql "drop table if exists #{lca_table_name}_#{id}"
72
- else
73
- # detach and forget about it
74
- lca_sql "alter table #{lca_table_name} detach partition #{ lca_role }"
75
- end
76
- end # delete_storage
46
+ if ACTIVE_TREE_OPTIONS[:destroy_partition_on_owner_destroy]
47
+ lca_sql "drop table if exists #{lca_table_name}_snapshots_#{id}"
48
+ else
49
+ lca_sql "alter table #{lca_table_name}_snapshots detach partition #{ lca_role }"
50
+ end
51
+
52
+ end
77
53
 
78
- def lca_sql sql
79
- ActiveRecord::Base.connection.execute sql
80
- end # lca_sql
81
54
 
55
+ def lca_sql(q)
56
+ ActiveRecord::Base.connection.execute q
57
+ end
82
58
  end # ClassMethods
83
59
  end
84
60
  end
@@ -1,6 +1,7 @@
1
1
  class Lca::Cycle::Product < Lca::Cycle
2
2
  # price?
3
3
  # quantity?
4
+ # quantity unit?
4
5
  # life time?
5
6
  # life time unit?
6
7
  end
@@ -1,4 +1,5 @@
1
1
  class Lca::Cycle::Service < Lca::Cycle
2
2
  # duration?
3
+ # duration unit?
3
4
  # price?
4
5
  end
@@ -1,8 +1,18 @@
1
1
  class Lca::Impact < Lca::Model
2
2
  belongs_to :exchange, class_name: "::Lca::Exchange", foreign_key: :parent_entity_id, required: true
3
3
 
4
+ has_many :snapshots, class_name: "::Lca::ImpactSnapshot", foreign_key: :impact_id, dependent: :destroy
5
+
4
6
  validates_presence_of :impact_amount
5
7
  validates_presence_of :impact_amount_unit, allow_blank: false
6
8
  validates_presence_of :impact_factor
7
9
  validates_presence_of :impact_precision
10
+
11
+ # lol?
12
+ before_update :snapshot!
13
+
14
+
15
+ def take_snapshot!
16
+ return ::Lca::ImpactSnapshot.take! self
17
+ end # .snapshot!
8
18
  end
@@ -0,0 +1,55 @@
1
+ class Lca::ImpactSnapshot < ActiveRecord::Base
2
+
3
+ self.primary_key = :id
4
+
5
+ def self.table_name
6
+ return "#{::ACTIVE_TREE_OPTIONS[:table_name]}_snapshots" if defined? ::ACTIVE_TREE_OPTIONS
7
+ return "active_tree_snapshots"
8
+ end
9
+
10
+
11
+ belongs_to :impact, class_name: "::Lca::Impact", foreign_key: :impact_id, required: true
12
+ belongs_to :owner, polymorphic: true, required: true
13
+
14
+ validates_presence_of :impact_amount
15
+ validates_presence_of :impact_amount_unit, allow_blank: false
16
+ validates_presence_of :impact_factor
17
+ validates_presence_of :impact_precision
18
+
19
+
20
+
21
+ # pull data from owner's partition
22
+ def self.owned_by(owner_id)
23
+ return self if !owner_id.is_a? Integer
24
+ partition_suffix = "_#{owner_id}"
25
+ table = "#{ self.table_name }#{ partition_suffix }"
26
+
27
+ ApplicationRecord.connection.schema_cache.clear!
28
+ return self if !ApplicationRecord.connection.schema_cache.data_source_exists? table
29
+
30
+ model_class = Class.new self
31
+
32
+ original_class_name = self.name
33
+ class_name = "#{name}#{partition_suffix}"
34
+
35
+ model_class.define_singleton_method(:table_name) do
36
+ table
37
+ end
38
+
39
+ model_class.define_singleton_method(:name) do
40
+ class_name
41
+ end
42
+
43
+ model_class
44
+ end # owned_by
45
+
46
+
47
+ def self.take!(impact)
48
+ return create(
49
+ impact.attributes
50
+ .deep_symbolize_keys
51
+ .slice(:impact_amount, :impact_amount_previous, :impact_amount_unit, :impact_factor, :impact_precision, :owner_id, :owner_type)
52
+ .merge( impact_id: impact.id )
53
+ )
54
+ end # .take!
55
+ end
@@ -1,75 +1,5 @@
1
- class Lca::Model < ActiveRecord::Base
1
+ class Lca::Model < ActiveTree::Model
2
2
 
3
- include Lca::Statusable
4
3
 
5
- self.primary_key = :id
6
-
7
- ltree :path
8
-
9
- def self.table_name
10
- return ::LCA_OPTIONS[:table_name] if defined? ::LCA_OPTIONS
11
- return "lca_models"
12
- end
13
-
14
- belongs_to :owner, polymorphic: :true, required: true
15
-
16
- scope :match_path, -> (some_path) { where("path ~ ?", "#{some_path}") }
17
-
18
- #has_one_attached :picture
19
- #has_rich_text :description
20
-
21
- validates_presence_of :name, allow_blank: false
22
- validates_presence_of :path, allow_blank: false
23
-
24
- before_validation :set_defaults
25
- def set_defaults
26
- self.path ||= name.delete(" ").gsub(/[^0-9a-z ]/i, '') if name
27
- self.path_slug = path.parameterize if path
28
- end
29
-
30
-
31
- # Scoping by owner in order to select the partition
32
- #
33
- # @param owner_id [Integer] the partition owner
34
- def self.owned_by(owner_id)
35
- # if we're looking for anything else but an integer, revert to the base class
36
- return self if !owner_id.is_a? Integer
37
-
38
- partition_suffix = "_#{owner_id}"
39
-
40
- table = "#{ self.table_name }#{ partition_suffix }"
41
-
42
- ApplicationRecord.connection.schema_cache.clear!
43
- return self if !ApplicationRecord.connection.schema_cache.data_source_exists? table
44
-
45
- # duplicate the class
46
- model_class = Class.new self
47
- original_class_name = self.name
48
-
49
- # ...for this owner
50
- class_name = "#{name}#{partition_suffix}"
51
-
52
- # specify the table
53
- model_class.define_singleton_method(:table_name) do
54
- table
55
- end
56
-
57
- # specify the name
58
- model_class.define_singleton_method(:name) do
59
- class_name
60
- end
61
-
62
- model_class.define_singleton_method(:sti_name) do
63
- original_class_name
64
- end
65
-
66
- # override the STI name lmfao
67
- model_class.define_singleton_method(:find_sti_class) do |p|
68
- original_class_name.constantize
69
- end
70
-
71
- # proceed
72
- model_class
73
- end # .owned_by
74
4
 
75
5
  end
@@ -1,2 +1,6 @@
1
1
  class Lca::Process::Transport < Lca::Process
2
- end
2
+ # distance?
3
+ # distance unit?
4
+ # duration?
5
+ # duration unit?
6
+ end
@@ -3,4 +3,9 @@ class Lca::Stage < Lca::Model
3
3
  belongs_to :cycle, class_name: "::Lca::Cycle", foreign_key: :parent_entity_id, required: true
4
4
  has_many :processes, class_name: "::Lca::Process", foreign_key: :parent_entity_id
5
5
 
6
+ # duration?
7
+ # duration unit?
8
+
9
+ # quantity?
10
+ # quantity unit?
6
11
  end
@@ -1,8 +1,3 @@
1
- class Lca::Queries::Query
2
-
3
- def valid_input?(v)
4
- return false if [ [], "", "0", 0, nil, [""], [0], ["0"], [nil], "all" ].include?(v)
5
- return true
6
- end
1
+ class Lca::Queries::Query < ActiveTree::Query
7
2
 
8
3
  end
data/lib/lca/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lca
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.9"
5
5
  end
data/lib/lca.rb CHANGED
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
- require "active_support/all"
3
2
 
3
+ require "active_support/all"
4
4
  require "active_record"
5
-
6
5
  require "pg_ltree"
6
+ require "active_tree"
7
7
 
8
8
  require_relative "lca/version"
9
9
 
10
- require_relative "lca/models/concerns/statusable"
10
+ require_relative "lca/models/concerns/lcable"
11
11
  require_relative "lca/models/model"
12
12
  require_relative "lca/models/cycle"
13
13
  require_relative "lca/models/stage"
14
14
  require_relative "lca/models/process"
15
15
  require_relative "lca/models/exchange"
16
16
  require_relative "lca/models/impact"
17
+ require_relative "lca/models/impact_snapshot"
17
18
  require_relative "lca/models/cycle/product"
18
19
  require_relative "lca/models/cycle/service"
19
20
  require_relative "lca/models/cycle/activity"
@@ -37,39 +38,12 @@ require_relative "lca/models/impact/ecosphere/flora"
37
38
  require_relative "lca/models/impact/human_health/cancer"
38
39
  require_relative "lca/models/impact/technosphere/resource_availability"
39
40
 
40
- require_relative "lca/models/concerns/lcable"
41
41
  require_relative "lca/active_record"
42
42
 
43
- # TODO add query objects and builders
44
- #require "lca/queries/"
45
- #require "lca/builders/"
46
43
 
47
44
  module Lca
48
45
  class Error < StandardError; end
49
46
 
50
- # Your code goes here...
51
-
52
- class << self
53
- attr_accessor :lca_models
54
- attr_accessor :options
55
- end
56
-
57
- self.lca_models = []
58
-
59
- def self.lca_options
60
- @options ||= begin
61
- path = Rails.root.join("config", "lca.yml").to_s
62
- if File.exist?(path)
63
- YAML.load(ERB.new(File.read(path)).result)
64
- else
65
- {
66
- table_name: "lca_models",
67
- jwt_secret: "",
68
- jwt_encryption: "HS256"
69
- }
70
- end
71
- end
72
- end
73
47
 
74
48
  def self.env
75
49
  @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick @ Earthster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-25 00:00:00.000000000 Z
11
+ date: 2021-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,33 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '6.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: activestorage
28
+ name: active_tree
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '6.0'
33
+ version: 0.2.8
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '6.0'
41
- - !ruby/object:Gem::Dependency
42
- name: actiontext
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '6.0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '6.0'
40
+ version: 0.2.8
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: pg_ltree
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -97,14 +83,10 @@ files:
97
83
  - bin/console
98
84
  - bin/setup
99
85
  - lib/generators/lca/install_generator.rb
100
- - lib/generators/lca/templates/config.yml.tt
101
- - lib/generators/lca/templates/initializer.rb.tt
102
86
  - lib/generators/lca/templates/migration.rb.tt
103
87
  - lib/lca.rb
104
88
  - lib/lca/active_record.rb
105
- - lib/lca/builders/builder.rb
106
89
  - lib/lca/models/concerns/lcable.rb
107
- - lib/lca/models/concerns/statusable.rb
108
90
  - lib/lca/models/cycle.rb
109
91
  - lib/lca/models/cycle/activity.rb
110
92
  - lib/lca/models/cycle/product.rb
@@ -118,6 +100,7 @@ files:
118
100
  - lib/lca/models/impact/human_health/cancer.rb
119
101
  - lib/lca/models/impact/technosphere.rb
120
102
  - lib/lca/models/impact/technosphere/resource_availability.rb
103
+ - lib/lca/models/impact_snapshot.rb
121
104
  - lib/lca/models/model.rb
122
105
  - lib/lca/models/process.rb
123
106
  - lib/lca/models/process/raw_resource.rb
@@ -1,18 +0,0 @@
1
- # the main table will be called "lca_models"
2
- # partitions will be called "lca_models_X" where X is the owner object ID
3
- table_name: "lca_models"
4
-
5
- # create PG roles for postgrest: anon and owner-specific roles
6
- create_postgrest_roles: true
7
-
8
- # on owner removal, detach the partition but preserve the table and data, or destroy the partition and data
9
- # if you choose to detach and preserve, in order to avoid table name collisions, make sure owner IDs are not reused
10
- destroy_partition_on_owner_destroy: true
11
-
12
- # jwt secret required for postgrest role switching
13
- jwt_secret: "supersecret"
14
- jwt_encryption: "HS256"
15
-
16
- # suffix postgres roles with a random string
17
- # to avoid collisions between other LCA installations in other apps using same db server
18
- owner_role_suffix: "changeme"
@@ -1,17 +0,0 @@
1
- LCA_OPTIONS ||= begin
2
- path = Rails.root.join("config", "lca.yml").to_s
3
- if File.exist?(path)
4
- YAML.load( ERB.new(File.read(path)).result ).deep_symbolize_keys
5
- else
6
- {
7
- table_name: "lca_models",
8
- create_postgrest_roles: true,
9
- jwt_secret: "supersecret",
10
- jwt_encryption: "HS256",
11
- destroy_partition_on_owner_destroy: true,
12
- owner_role_suffix: "changeme"
13
- }
14
- end
15
- end.merge({
16
- database_user: Rails.application.config.database_configuration[ Rails.env ].deep_symbolize_keys[:username]
17
- })
@@ -1,3 +0,0 @@
1
- class Lca::Builder
2
-
3
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
- module Lca::Statusable
3
-
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- scope :active, -> { where(status: 1) }
8
- scope :inactive, -> { where(status: 0) }
9
- alias_method :enabled?, :active?
10
- alias_method :enable!, :active!
11
- alias_method :on!, :active!
12
- alias_method :disabled?, :inactive?
13
- alias_method :disable!, :inactive!
14
- alias_method :off!, :inactive!
15
- alias_method :toggle?, :toggle_status!
16
- end
17
-
18
-
19
- def toggle_status!
20
- if active?
21
- inactive!
22
- else
23
- active!
24
- end
25
- end
26
-
27
- def status?
28
- [:inactive, :active][ status ]
29
- end
30
-
31
- def active?
32
- status == 1
33
- end
34
- def inactive?
35
- status == 0
36
- end
37
-
38
- def active!
39
- self.update(status: 1)
40
- end
41
- def inactive!
42
- self.update(status: 0)
43
- end
44
- end