labimotion 2.1.0.rc4 → 2.1.0.rc5

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: e0c3346ede1f92148d84551b0c4052848803e1b6969d14263a771b0fdfe8f2f1
4
- data.tar.gz: df2597e134e8adf3e4ff48775578ae507d6c4b022c4abfd368cf01bd53ff1569
3
+ metadata.gz: e23b274f29ceb300bbb97f91830dbe2fdc454966d229e88608cd434b6586a3b3
4
+ data.tar.gz: da620e95cb04ce10a69cba25d8cb63f22cb1e21142a1a6a3c93d9fecc33840aa
5
5
  SHA512:
6
- metadata.gz: 80d8a6c8d32136057e1cbf43a8bfe0526665cc303bb976492f886454561f39ee9bf7d2bc9be6502a3648ab52420e1b37c171a463776ee97d72470e7a6c192555
7
- data.tar.gz: 3e4bc196612beac1ad2f8c62bbb608fd1b646385135c0ba3612cd7dc762467a3e0bd73d242b53046cb9a781f8b6214424d5a0a2dee357929fb0eb92712778174
6
+ metadata.gz: db1827e8ad8c56f05c55215f3cd5e3830174f937875c2af49e7899dc4875180364111b1788eecc430b6f3ca8b39e3e0234eb21a3c1036c3b7ab064676f8bb4d1
7
+ data.tar.gz: 3f1667aa18030745ab178330cac70f234d4aa3eab46e3622546aeb4268aaf379e9d8dd0369060324327dd3db67efa2da6e94b6568feb33a88e987bec8030a737
@@ -87,6 +87,37 @@ module Labimotion
87
87
  end
88
88
  end
89
89
 
90
+ namespace :search_basic_by_like do
91
+ desc 'Search basic elements by name and short label (case-insensitive like search)'
92
+ params do
93
+ requires :klass_name, type: String, desc: 'Class name (device_description or wellplate or ...etc.)', default: 'device_description'
94
+ requires :name, type: String, desc: 'Search query for basic element name'
95
+ requires :short_label, type: String, desc: 'Search query for basic element short label'
96
+ optional :limit, type: Integer, desc: 'Maximum number of results', default: 20
97
+ end
98
+ get do
99
+ # Convert snake_case to PascalCase (e.g. device_description -> DeviceDescription)
100
+ klass_name = params[:klass_name].camelize
101
+ klass = "Labimotion::#{klass_name}".constantize
102
+
103
+ scope = klass.fetch_for_user(
104
+ current_user.id,
105
+ name: params[:name],
106
+ short_label: params[:short_label],
107
+ limit: params[:limit]
108
+ )
109
+
110
+ results = scope.map do |record|
111
+ Labimotion::ElementLookupEntity.represent(record)
112
+ end
113
+
114
+ { elements: results, total_count: results.count }
115
+ rescue StandardError => e
116
+ Labimotion.log_exception(e, current_user)
117
+ { elements: [], total_count: 0, error: e.message }
118
+ end
119
+ end
120
+
90
121
  namespace :export do
91
122
  desc 'export element'
92
123
  params do
@@ -253,12 +284,13 @@ module Labimotion
253
284
  desc 'list Generic Element Klass'
254
285
  params do
255
286
  optional :is_generic, type: Boolean, desc: 'Is Generic or Non-Generic Element'
256
- optional :is_active, type: Boolean, desc: 'Active or Inactive Dataset'
287
+ optional :is_active, type: Boolean, desc: 'Active or Inactive'
257
288
  optional :displayed_in_list, type: Boolean, desc: 'Display in list format', default: true
258
289
  end
259
290
  get do
260
291
  scope = params[:displayed_in_list] ? Labimotion::ElementKlass.for_list_display : Labimotion::ElementKlass.all
261
292
  scope = scope.where(is_generic: params[:is_generic]) if params.key?(:is_generic)
293
+ scope = scope.where(is_active: params[:is_active]) if params.key?(:is_active)
262
294
 
263
295
  list = scope.sort_by(&:place)
264
296
  present list, with: Labimotion::ElementKlassEntity, root: 'klass', displayed_in_list: params[:displayed_in_list]
@@ -498,8 +530,12 @@ module Labimotion
498
530
  class ElementLookupEntity < Grape::Entity
499
531
  expose :id
500
532
  expose :name
501
- expose :short_label
502
- expose :element_klass_id
533
+ expose :short_label do |element|
534
+ element.respond_to?(:short_label) ? element.short_label : nil
535
+ end
536
+ expose :element_klass_id, as: :element_klass_id do |element|
537
+ element.element_klass&.id
538
+ end
503
539
  expose :klass_label, as: :klass_label do |element|
504
540
  element.element_klass&.label
505
541
  end
@@ -113,11 +113,12 @@ module Labimotion
113
113
  Labimotion::ElementsElement.find_or_create_by(parent_id: element.id, element_id: el.id)
114
114
  els << el.id
115
115
  end
116
-
117
116
  end
118
117
  if element.present?
119
- es_list = Labimotion::ElementsSample.where(element_id: element.id).where.not(sample_id: sds)
120
- ee_list = Labimotion::ElementsElement.where(parent_id: element.id).where.not(element_id: els&.flatten)
118
+ sds = sds.flatten.uniq
119
+ els = els.flatten.uniq
120
+ es_list = sds.present? ? Labimotion::ElementsSample.where(element_id: element.id).where.not(sample_id: sds) : Labimotion::ElementsSample.where(element_id: element.id)
121
+ ee_list = els.present? ? Labimotion::ElementsElement.where(parent_id: element.id).where.not(element_id: els) : Labimotion::ElementsElement.where(parent_id: element.id)
121
122
  es_list.destroy_all if es_list.present?
122
123
  ee_list.destroy_all if ee_list.present?
123
124
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Labimotion
4
+ module ElementFetchable
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def element_klass
9
+ Labimotion::ElementKlass.find_by(name: element_klass_name)
10
+ end
11
+
12
+ def fetch_for_user(user_id, name: nil, short_label: nil, limit: 20)
13
+ # Prevent abuse by capping and validating limit
14
+ limit = [limit.to_i, 100].min
15
+ limit = 20 if limit <= 0
16
+
17
+ # Build base scope with common filters
18
+ apply_filters = lambda do |scope|
19
+ scope = scope.where("#{table_name}.name ILIKE ?", "%#{sanitize_sql_like(name)}%") if name.present?
20
+ scope = scope.where("#{table_name}.short_label ILIKE ?", "%#{sanitize_sql_like(short_label)}%") if short_label.present? && column_names.include?('short_label')
21
+ scope
22
+ end
23
+
24
+ # Owned records
25
+ owned = apply_filters.call(
26
+ joins(collections: :user).where(collections: { user_id: user_id })
27
+ )
28
+
29
+ # Shared (synced) records
30
+ shared = apply_filters.call(
31
+ joins(collections: :sync_collections_users).where(sync_collections_users: { user_id: user_id })
32
+ )
33
+
34
+ # Combine (remove duplicates), order, and limit
35
+ order_column = column_names.include?('short_label') ? :short_label : :name
36
+ from("(#{owned.to_sql} UNION #{shared.to_sql}) AS #{table_name}")
37
+ .order(order_column => :desc)
38
+ .limit(limit)
39
+ end
40
+
41
+ private
42
+
43
+ def element_klass_name
44
+ raise NotImplementedError, "Subclass must define element_klass_name"
45
+ end
46
+ end
47
+
48
+ # Instance method to get element_klass for a record
49
+ def element_klass
50
+ self.class.element_klass
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing DeviceDescription model in the consuming app
4
+ # and defines a Labimotion::DeviceDescription wrapper for convenient access.
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ if defined?(::DeviceDescription)
8
+ ::DeviceDescription.class_eval do
9
+ include Labimotion::ElementFetchable
10
+
11
+ def self.element_klass_name
12
+ 'device_description'
13
+ end
14
+ end
15
+ else
16
+ warn "[Labimotion] DeviceDescription is not defined when Labimotion extension was loaded."
17
+ end
18
+ end
19
+
20
+ # Namespace wrapper to keep your preferred call style
21
+ module Labimotion
22
+ module DeviceDescription
23
+ # Delegate class methods to ::DeviceDescription
24
+ def self.method_missing(method, *args, &block)
25
+ if ::DeviceDescription.respond_to?(method)
26
+ ::DeviceDescription.public_send(method, *args, &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def self.respond_to_missing?(method, include_private = false)
33
+ ::DeviceDescription.respond_to?(method, include_private) || super
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing Reaction model in the consuming app
4
+ # and defines a Labimotion::Reaction wrapper for convenient access.
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ if defined?(::Reaction)
8
+ ::Reaction.class_eval do
9
+ include Labimotion::ElementFetchable
10
+
11
+ def self.element_klass_name
12
+ 'reaction'
13
+ end
14
+ end
15
+ else
16
+ warn "[Labimotion] Reaction is not defined when Labimotion extension was loaded."
17
+ end
18
+ end
19
+
20
+ # Namespace wrapper to keep your preferred call style
21
+ module Labimotion
22
+ module Reaction
23
+ # Delegate class methods to ::Reaction
24
+ def self.method_missing(method, *args, &block)
25
+ if ::Reaction.respond_to?(method)
26
+ ::Reaction.public_send(method, *args, &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def self.respond_to_missing?(method, include_private = false)
33
+ ::Reaction.respond_to?(method, include_private) || super
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing ResearchPlan model in the consuming app
4
+ # and defines a Labimotion::ResearchPlan wrapper for convenient access.
5
+
6
+ # rubocop:disable Style/RedundantConstantBase
7
+
8
+ ActiveSupport.on_load(:active_record) do
9
+ if defined?(::ResearchPlan)
10
+ ::ResearchPlan.class_eval do
11
+ include Labimotion::ElementFetchable
12
+
13
+ def self.element_klass_name
14
+ 'research_plan'
15
+ end
16
+ end
17
+ else
18
+ warn '[Labimotion] ResearchPlan is not defined when Labimotion extension was loaded.'
19
+ end
20
+ end
21
+
22
+ # Namespace wrapper to keep your preferred call style
23
+ module Labimotion
24
+ module ResearchPlan
25
+ # Delegate class methods to ::ResearchPlan
26
+ def self.method_missing(method, *args, &block)
27
+ if ::ResearchPlan.respond_to?(method)
28
+ ::ResearchPlan.public_send(method, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def self.respond_to_missing?(method, include_private = false)
35
+ ::ResearchPlan.respond_to?(method, include_private) || super
36
+ end
37
+ end
38
+ end
39
+
40
+ # rubocop:enable Style/RedundantConstantBase
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing Sample model in the consuming app
4
+ # and defines a Labimotion::Sample wrapper for convenient access.
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ if defined?(::Sample)
8
+ ::Sample.class_eval do
9
+ include Labimotion::ElementFetchable
10
+
11
+ def self.element_klass_name
12
+ 'sample'
13
+ end
14
+ end
15
+ else
16
+ warn "[Labimotion] Sample is not defined when Labimotion extension was loaded."
17
+ end
18
+ end
19
+
20
+ # Namespace wrapper to keep your preferred call style
21
+ module Labimotion
22
+ module Sample
23
+ # Delegate class methods to ::Sample
24
+ def self.method_missing(method, *args, &block)
25
+ if ::Sample.respond_to?(method)
26
+ ::Sample.public_send(method, *args, &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def self.respond_to_missing?(method, include_private = false)
33
+ ::Sample.respond_to?(method, include_private) || super
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing Screen model in the consuming app
4
+ # and defines a Labimotion::Screen wrapper for convenient access.
5
+
6
+ ActiveSupport.on_load(:active_record) do
7
+ if defined?(::Screen)
8
+ ::Screen.class_eval do
9
+ include Labimotion::ElementFetchable
10
+
11
+ def self.element_klass_name
12
+ 'screen'
13
+ end
14
+ end
15
+ else
16
+ warn "[Labimotion] Screen is not defined when Labimotion extension was loaded."
17
+ end
18
+ end
19
+
20
+ # Namespace wrapper to keep your preferred call style
21
+ module Labimotion
22
+ module Screen
23
+ # Delegate class methods to ::Screen
24
+ def self.method_missing(method, *args, &block)
25
+ if ::Screen.respond_to?(method)
26
+ ::Screen.public_send(method, *args, &block)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def self.respond_to_missing?(method, include_private = false)
33
+ ::Screen.respond_to?(method, include_private) || super
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file extends the existing Wellplate model in the consuming app
4
+ # and defines a Labimotion::Wellplate wrapper for convenient access.
5
+
6
+ # rubocop:disable Style/RedundantConstantBase
7
+
8
+ ActiveSupport.on_load(:active_record) do
9
+ if defined?(::Wellplate)
10
+ ::Wellplate.class_eval do
11
+ include Labimotion::ElementFetchable
12
+
13
+ def self.element_klass_name
14
+ 'wellplate'
15
+ end
16
+ end
17
+ else
18
+ warn '[Labimotion] Wellplate is not defined when Labimotion extension was loaded.'
19
+ end
20
+ end
21
+
22
+ # Namespace wrapper to keep your preferred call style
23
+ module Labimotion
24
+ module Wellplate
25
+ # Delegate class methods to ::Wellplate
26
+ def self.method_missing(method, *args, &block)
27
+ if ::Wellplate.respond_to?(method)
28
+ ::Wellplate.public_send(method, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def self.respond_to_missing?(method, include_private = false)
35
+ ::Wellplate.respond_to?(method, include_private) || super
36
+ end
37
+ end
38
+ end
39
+
40
+ # rubocop:enable Style/RedundantConstantBase
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.1.0.rc4'
5
+ VERSION = '2.1.0.rc5'
6
6
  end
data/lib/labimotion.rb CHANGED
@@ -113,9 +113,17 @@ module Labimotion
113
113
  autoload :StdLayer, 'labimotion/models/std_layer'
114
114
  autoload :StdLayersRevision, 'labimotion/models/std_layers_revision'
115
115
 
116
+ autoload :DeviceDescription, 'labimotion/models/device_description'
117
+ autoload :Reaction, 'labimotion/models/reaction'
118
+ autoload :ResearchPlan, 'labimotion/models/research_plan'
119
+ autoload :Sample, 'labimotion/models/sample'
120
+ autoload :Screen, 'labimotion/models/screen'
121
+ autoload :Wellplate, 'labimotion/models/wellplate'
122
+
116
123
  ######## Models/Concerns
117
124
  autoload :GenericKlassRevisions, 'labimotion/models/concerns/generic_klass_revisions'
118
125
  autoload :GenericRevisions, 'labimotion/models/concerns/generic_revisions'
126
+ autoload :ElementFetchable, 'labimotion/models/concerns/element_fetchable'
119
127
  autoload :Segmentable, 'labimotion/models/concerns/segmentable'
120
128
  autoload :Datasetable, 'labimotion/models/concerns/datasetable'
121
129
  autoload :AttachmentConverter, 'labimotion/models/concerns/attachment_converter.rb'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: labimotion
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0.rc4
4
+ version: 2.1.0.rc5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chia-Lin Lin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-10-20 00:00:00.000000000 Z
12
+ date: 2025-10-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: caxlsx
@@ -116,6 +116,7 @@ files:
116
116
  - lib/labimotion/models/collections_element.rb
117
117
  - lib/labimotion/models/concerns/attachment_converter.rb
118
118
  - lib/labimotion/models/concerns/datasetable.rb
119
+ - lib/labimotion/models/concerns/element_fetchable.rb
119
120
  - lib/labimotion/models/concerns/generic_klass.rb
120
121
  - lib/labimotion/models/concerns/generic_klass_revisions.rb
121
122
  - lib/labimotion/models/concerns/generic_revisions.rb
@@ -126,6 +127,7 @@ files:
126
127
  - lib/labimotion/models/dataset_klass.rb
127
128
  - lib/labimotion/models/dataset_klasses_revision.rb
128
129
  - lib/labimotion/models/datasets_revision.rb
130
+ - lib/labimotion/models/device_description.rb
129
131
  - lib/labimotion/models/element.rb
130
132
  - lib/labimotion/models/element_klass.rb
131
133
  - lib/labimotion/models/element_klasses_revision.rb
@@ -133,6 +135,10 @@ files:
133
135
  - lib/labimotion/models/elements_revision.rb
134
136
  - lib/labimotion/models/elements_sample.rb
135
137
  - lib/labimotion/models/hub_log.rb
138
+ - lib/labimotion/models/reaction.rb
139
+ - lib/labimotion/models/research_plan.rb
140
+ - lib/labimotion/models/sample.rb
141
+ - lib/labimotion/models/screen.rb
136
142
  - lib/labimotion/models/segment.rb
137
143
  - lib/labimotion/models/segment_klass.rb
138
144
  - lib/labimotion/models/segment_klasses_revision.rb
@@ -140,6 +146,7 @@ files:
140
146
  - lib/labimotion/models/std_layer.rb
141
147
  - lib/labimotion/models/std_layers_revision.rb
142
148
  - lib/labimotion/models/vocabulary.rb
149
+ - lib/labimotion/models/wellplate.rb
143
150
  - lib/labimotion/utils/con_state.rb
144
151
  - lib/labimotion/utils/export_utils.rb
145
152
  - lib/labimotion/utils/field_type.rb