ROR : Workflow gem : I want to implement dynamic states from database - ruby-on-rails

I am currrently working on a project that has to implement dynamic workflow.
Dynamic: I store workflow's states in database table called wf_steps and the workflow gem has to create states for a particular workflow from the database
For that I am trying to use the workflow gem. You can see how it initializes states and corresponding events in the gem's github-page.
My code:
class SpsWorkflow < ActiveRecord::Base
belongs_to :user
has_many :wf_steps
include Workflow
workflow do
# binding.pry
# read wf-states from the database
# for now let event be same for all the states
self.wf_steps.each do |step|
state step.title.to_sym do
event :assign, transitions_to: :assigning
event :hire, transitions_to: :hiring
event :not_hire, transitions_to: :not_hiring
end
end
end
end
Expectation and Problem encountered:
I expected in the code block below the term self.wf_steps would return my SpsWorkflow's instance/collection. However the self keyword returns #<Workflow::Specification:0x000000063e23e8 #meta={}, #states={}> when I use binding.pry inside the workflow method's block ( I commented in the code )
# read wf-states from the database
# for now let event be same for all the states
self.wf_steps.each do |step|
state step.title.to_sym do
Need you help, thanks
EDIT:
I also tried storing the instance in a variable and using the variable inside the block passing to the workflow method call.
class SpsWorkflow < ActiveRecord::Base
include Workflow
sps_instance = self
But I got the instance of the class SpsWorkflow like
SpsWorkflow(id: integer, workflow_state: string, assigned_to: integer, title: string, description: string, organization_id: integer, user_id: integer, created_at: datetime, updated_at: datetime)
but I want
#<SpsWorkflow id: 1, workflow_state: "step1", assigned_to: nil, title: "Software Engineer", description: "Hire best engineer", organization_id: nil, user_id: 1, created_at: "2015-08-08 00:58:12", updated_at: "2015-08-08 00:58:12">

You have used:
workflow do
self.something
end
self in the context of this block will refer to the WorkflowSpecification. If you really want access to the instance of SpsWorkflow, you may have to pass it into the block or assign it to a different variable and use it there.

I finally solved it using a activerecord callback
class SpsWorkflow < ActiveRecord::Base
include Workflow
after_initialize do
sps_instance = self
SpsWorkflow.workflow do
# read wf-states as well as events from the database
sps_instance.wf_steps.each do |step|
state step.title.to_sym do
event :assign, transitions_to: :step2
event :hire, transitions_to: :hire
event :not_hire, transitions_to: :not_hiring
end
end
end
end
belongs_to :user
has_many :wf_steps
end

Related

Unable to set Rails model attribute from console or controller

I'm new to Rails and am working on getting an application set up in Rails 4.2.4. I have a model called List that looks like the following in the database (PostgresQL):
List(id: integer, user_id: integer, name: string, created_at: datetime, updated_at: datetime, friendly_name: string)
and in List.rb:
class List < ActiveRecord::Base
attr_accessor :name, :friendly_name
belongs_to :user
has_many :items
end
I am trying to modify the name attribute from a controller action:
def save_name
...
list_to_edit = List.find(params[:id].to_i)
list_to_edit.name = params[:name]
list_to_edit.save!
...
end
But the changes are not being persisted. I have confirmed that params[:name] and list_to_edit are not nil. When I try to change the attribute in the Rails console like this:
> l = List.last
> l.name = 'TestName'
> l.save!
I don't see any errors. After executing the above commands and executing l.name I do see TestName. When I type l or List.last, however I still see
#<List id: 29, user_id: 17, name: nil, created_at: "2015-11-07 18:55:04", updated_at: "2015-11-07 18:55:04", friendly_name: nil>
What do I need to do to set the name attribute of a List? I can post any additional file content if it is helpful.
After trying a few more things it looks like all I needed to do was remove name from the array being passed to attr_accessor in List.rb. I believe when I was trying to change the list name with my_list.name = 'something' I was modifying the instance variable, not the attribute stored in the database.

Rails nested resources and devise

Let's say I have a devise model called "User" which has_many :notes and :notebooks and each :notebook has_many :notes.
So a notes will have two foreign key, :user_id and :notebook_id, so how to build/find a note?
current_user.notebooks.find(param).notes.new(params[:item]) will create the foreign_key only for notebook or also for the user in the note record in the DB?
If the second case (foreign key only for notebook), how should I write?
Using MongoDB with MongoID and referenced relations
Mongoid will manage the document references and queries for you, just make sure to specify the association/relationship for each direction that you need (e.g., User has_many :notes AND Note belongs_to :user). Like ActiveRecord, it appears to be "smart" about the relations. Please do not manipulate the references manually, but instead let your ODM (Mongoid) work for you. As you run your tests (or use the rails console), you can tail -f log/test.log (or log/development.log) to see what MongoDB operations are being done by Mongoid for you and you can see the actual object references as the documents are updated. You can see how a relationship makes use of a particular object reference, and if you pay attention to this, the link optimization should become clearer.
The following models and test work for me. Details on the setup are available on request. Hope that this helps.
Models
class User
include Mongoid::Document
field :name
has_many :notebooks
has_many :notes
end
class Note
include Mongoid::Document
field :text
belongs_to :user
belongs_to :notebook
end
class Notebook
include Mongoid::Document
belongs_to :user
has_many :notes
end
Test
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
User.delete_all
Note.delete_all
Notebook.delete_all
end
test "user" do
user = User.create!(name: 'Charles Dickens')
note = Note.create!(text: 'It was the best of times')
notebook = Notebook.create!(title: 'Revolutionary France')
user.notes << note
assert_equal(1, user.notes.count)
user.notebooks << notebook
assert_equal(1, user.notebooks.count)
notebook.notes << note
assert_equal(1, notebook.notes.count)
puts "user notes: " + user.notes.inspect
puts "user notebooks: " + user.notebooks.inspect
puts "user notebooks notes: " + user.notebooks.collect{|notebook|notebook.notes}.inspect
puts "note user: " + note.user.inspect
puts "note notebook: " + note.notebook.inspect
puts "notebook user: " + notebook.user.inspect
end
end
Result
Run options: --name=test_user
# Running tests:
user notes: [#<Note _id: 4fa430937f11ba65ce000002, _type: nil, text: "It was the best of times", user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), notebook_id: BSON::ObjectId('4fa430937f11ba65ce000003')>]
user notebooks: [#<Notebook _id: 4fa430937f11ba65ce000003, _type: nil, user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), title: "Revolutionary France">]
user notebooks notes: [[#<Note _id: 4fa430937f11ba65ce000002, _type: nil, text: "It was the best of times", user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), notebook_id: BSON::ObjectId('4fa430937f11ba65ce000003')>]]
note user: #<User _id: 4fa430937f11ba65ce000001, _type: nil, name: "Charles Dickens">
note notebook: #<Notebook _id: 4fa430937f11ba65ce000003, _type: nil, user_id: BSON::ObjectId('4fa430937f11ba65ce000001'), title: "Revolutionary France">
notebook user: #<User _id: 4fa430937f11ba65ce000001, _type: nil, name: "Charles Dickens">
.
Finished tests in 0.018622s, 53.6999 tests/s, 161.0998 assertions/s.
1 tests, 3 assertions, 0 failures, 0 errors, 0 skips
I would use
class User
has_many :notebooks
has_many :notes, :through => :notebooks
end
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Update
You could always set the user_id manually, like this (I assume param is the ID for your notebook?):
Notebook.find(param).notes.new(params[:item].merge(:user_id => current_user.id))

Rails: Why don't the child classes get loaded when using class method to return attributes about child classes?

Hello I've been searching for a solution for this for a while. Using Rails 2.3.5
I have a parent class with several child classes and for the sake of not having a file that's 1500 lines long I have the child classes kept in a subdirectory of the app/models directory.
Up until recently when I viewed this post: here
I couldn't even get the child classes to load
Now I want access each child in a manner using the self.inherited class method
like this:
class Project < ActiveRecord::Base
CHILDREN = []
def self.inherited(child)
super
CHILDREN << child
puts "CHILDREN.inspect: #{CHILDREN.inspect}"
end
def self.valid_child_types
CHILDREN.collect{ |child| child.project_type}
end
end
Temporarily, I put some debug statements to get a better picture of how things are getting loaded. I fired up the console and noticed this behavior:
>> Project
require_or_load /Users/frankdrebin/Sites/cerp/app/models/project.rb
loading /Users/frankdrebin/Sites/cerp/app/models/project
require_or_load /Users/frankdrebin/Sites/cerp/app/models/status.rb
loading /Users/frankdrebin/Sites/cerp/app/models/status
=> Project(id: integer, url: string, deadline: date, state: string, type: string, priority: integer, status_id: integer)
>> Project::CHILDREN
=> []
>> ArticleProject
require_or_load /Users/frankdrebin/Sites/cerp/app/models/projects/article_project.rb
loading /Users/frankdrebin/Sites/cerp/app/models/projects/article_project
CHILDREN.inspect: [ArticleProject(id: integer, url: string, deadline: date, state: string, type: string, priority: integer, status_id: integer)]
require_or_load /Users/frankdrebin/Sites/cerp/vendor/gems/state_machine- 0.7.3/lib/state_machine.rb
loading /Users/frankdrebin/Sites/cerp/vendor/gems/state_machine-0.7.3/lib/state_machine
=> ArticleProject(id: integer, url: string, deadline: date, state: string, type: string, priority: integer, status_id: integer)
>> Project::CHILDREN
=> [ArticleProject(id: integer, url: string, deadline: date, state: string, type: string, priority: integer, status_id: integer)]
>>
I am sure there are less elegant solutions to this, such as putting the Child Classes all back into one gigantic file but I'd like to avoid this if at all possible.
Thanks
You have all sorts of problems:
CHILDREN is a constant in Ruby because it starts with a capital letter, you don't want that.
Next if you change it to children it would then be a local variable but you need an instance variable for the instance of the definition of the parent class so you need to use #children.
You need to make the #children availible at the class level (which is probably why you were trying the CHILDREN thing).
Here's how you would do it:
class Parent
#children = []
# Make the attr_reader for the class not an instance of the class
class << self
attr_reader :children
end
def self.inherited(child)
puts "Parent inherited by child: #{child.inspect}"
#children << child
super
end
end
class Child1 < Parent
end
puts "Child1 class created"
class Child2 < Parent
end
puts "Child2 class created"
c1 = Child1.new
c2 = Child2.new
puts "Parent.children: #{Parent.children}"
Output:
Parent inherited by child: Child1
Child1 class created
Parent inherited by child: Child2
Child2 class created
Parent.children: [Child1, Child2]
Perhaps you need to take a step back and ask yourself what you're really trying to accomplish, and is inheritance the best/only way? Modules/mixins?

How to create read-only virtual attributes in Ruby models

I would like to create a virtual attribute that will always be included when you do model_instance.inspect. I understand that attr_reader will give me the same thing as just defining an instance method, but I would like this attribute to be part of the object's "make up"
How can I accomplish this?
Thanks!
UPDATE
Here is what is not working in more detail:
class Product < ActiveRecord::Base
attr_reader :less_secure_asset_url
def less_secure_asset_url
self.asset.url
end
end
>> p = Product.find(:all)[1]
=> #<Product id: 49, asset_file_name: "Etrade_Trade_Conf_Request.docx", asset_content_type: "application/vnd.openxmlformats-officedocument.wordp...", asset_file_size: 38152, asset_updated_at: "2010-05-04 17:45:46", created_at: "2010-05-04 17:45:46", updated_at: "2010-05-04 17:45:46", owner_id: 345, product_type_id: 1>
As you can see, when I use the console it returns no "less_secure_asset_url" attribute
Your attribute is in there, even if it doesn't show up. Rails overrides the definition of the inspect method of Object in ActiveRecord::Base to something like:
def inspect
attributes_as_nice_string = self.class.column_names.collect { |name|
if has_attribute?(name) || new_record?
"#{name}: #{attribute_for_inspect(name)}"
end
}.compact.join(", ")
"#<#{self.class} #{attributes_as_nice_string}>"
end
Basically, if it's not a column_name, it's not going to show up. I haven't tried this, but you might be able to call something like p.as(Object).inspect to get to the super classes inspect method. You have to require 'facets' to get as. See docs here.

ActiveRecord derived attribute persistence which depends on id value

How do you persist a derived attribute which depends on the value of id in rails? The snippet below seems to work-- Is there a better rails way?
class Model < ActiveRecord::Base
....
def save
super
#derived_attr column exists in DB
self.derived_attr = compute_attr(self.id)
super
end
end
Callbacks are provided so you should never have to override save. The before_save call in the following code is functionally equivalent to all the code in the question.
I've made set_virtual_attr public so that it can be calculated as needed.
class Model < ActiveRecord::Base
...
# this one line is functionally equivalent to the code in the OP.
before_save :set_virtual_attr
attr_reader :virtual_attr
def set_virtual_attr
self.virtual_attr = compute_attr(self.id)
end
private
def compute_attr
...
end
end
I think the more accepted way to do this would be to provide a custom setter for the virtual attribute and then provide an after_create hook to set the value after the record is created.
The following code should do what you want.
class Virt < ActiveRecord::Base
def after_create()
self.virtual_attr = nil # Set it to anything just to invoke the setter
save # Saving will not invoke this callback again as the record exists
# Do NOT try this in after_save or you will get a Stack Overflow
end
def virtual_attr=(value)
write_attribute(:virtual_attr, "ID: #{self.id} #{value}")
end
end
Running this in the console shows the following
v=Virt.new
=> #<Virt id: nil, virtual_attr: nil, created_at: nil, updated_at: nil>
>> v.save
=> true
>> v
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">
>> Virt.last
=> #<Virt id: 8, virtual_attr: "ID: 8 ", created_at: "2009-12-23 09:25:17",
updated_at: "2009-12-23 09:25:17">

Resources