Ruby on Rails: Polymorphic/Object-agnostic conditions on Trigger setups? - ruby-on-rails

*I'm not really sure what concept this problem would go under - would this be considered polymorphic associations?
I want to create a system where users can set up triggers so that after a certain event happens, another event happens.
For example, I want the user to be able to define a trigger action as:
"This [Email, Document] must be sent to all users after they [Sign up, read email A, sign document B]."
As you can see within the brackets "[]", the conditions can be tied to various objects and actions.
My thought was to create a model TriggerCondition and storing columns
:object_id, :assigned_to, :activated_when
but the object_id(Email, Document) can be different models.
Would adding a column :object_type that can be either email or document be the best solution? Or is there a more sophisticated way of designing so that I can pass in an object and it will automatically know what I'm referring to?
Regarding the :activated_when condition, I'm completely unsure how I would store and check for condition. Would I be able to use a syntax similar to "Email#marked_as_read(A)?"
I'm trying to use Rails Observers to trigger the action
http://api.rubyonrails.org/v3.2.13/classes/ActiveRecord/Observer.html

Rails is built to handle polymorphic associations just like this.

Related

Dry up Rails Active Record query conditions

In my Ruby on Rails project, I have a mailer that basically prepares a daily digest of things that happened in the system for a given user. In the mailer controller, I am gathering all the relevant records from the various models according to some common pattern (within a certain date, not authored by this user, not flagged, etc) and with minor differences from model to model.
There are half a dozen of models involved here (and counting), and most of them have unified column names for certain things (like date of publishing, or whether an item is flagged by admin or not). Hence, the 'where's that go into query are mostly the same. There are minor differences in conditions, but at least 2 or 3 conditions are exactly the same. I easily assume there may be even more similar conditions between models, since we are just starting the feature and haven't figured out the eventual shape of the data yet.
I basically chain the 'where' calls upon each model. It irritates me to have 6 lines of code so close to each other, spanning so far to the right of my code editor, and yet so similar. I am dreaded by the idea that at some point we will have to change one of the 'core' conditions, munging with that many lines of code all at once.
What I'd love to do is to move a core set of conditions that goes into each query into some sort of Proc or whatever, then simply call it upon each model like a scope, and after that continue the 'where' chain with model-specific conditions. Much like a scope on each model.
What I am struggling with is how exactly to do that, while keeping the code inside mailer. I certainly know that I can declare a complex scope inside a concern, then mix it into my models and start each of queries with that scope. However, this way the logic will go away from the mailer into an uncharted territory of model concerns, and also it will complicate each model with a scope that is currently only needed for one little mailer in a huge system. Also, for some queries, a set of details from User model is required for a query, and I don't want each of my models to handle User.
I like the way scopes are defined in the Active Record models via lambdas (like scope :pending, -> { where(approved: [nil, false]) }), and was looking for a way to use similar syntax outside model class and inside my mailer method (possibly with a tap or something like that), but I haven't found any good examples of such an approach.
So, is it possible to achieve? Can I collect the core 'where' calls inside some variable in my mailer method and apply them to many models, while still being able to continue the where chain after that?
The beauty of Arel, the technology behind ActiveRecord query-building, is it's all completely composable, using ordinary ruby.
Do I understand your question right that this is what you want to do?
def add_on_something(arel_scope)
arel_scope.where("magic = true").where("something = 1")
end
add_on_something(User).where("more").order("whatever").limit(10)
add_on_something( Project.where("whatever") ).order("something")
Just ordinary ruby method will do it, you don't need a special AR feature. Because AR scopes are already composable.
You could do something like:
#report_a = default_scope(ModelA)
#report_b = default_scope(ModelB)
private
def default_scope(model)
model.
where(approved: [nil, false]).
order(:created_at)
# ...
end

How do I create a fields_for a potentially unlimited number of children objects?

I'm making an application that involves booking appointments for users. I have a User model and an AvailableDate model. The user has_many availble_dates and the AvailableDate belongs_to user.
I want to present a form for the user so that they can mark a couple of dates in a calendar and each of the dates they mark will become an AvailableDate object tied to that user.
At the moment my solution is to do all the work that a form_for helper would normally do manually. This involves a lot of javascript and is generally just getting far too messy.
I can't figure out how I should make a form_for tag work when I need to create potentially infinitely many dates. In theory a user could keep marking off dates in the future as available. If I knew how many dates I needed to create for a user, I could do user.available_dates.build, N times. But this doesn't work here.
Can anyone help? It like this problem should be pretty common. Am I designing my application wrong?
One technique is to render the fields for your association once, outside the form.
When the user performs whatever interaction that should create a new set of inputs you use javascript to clone the initial set of fields and insert them into the form. The one thing you need to do is change the name of these inputs so that they are unique. Usually people use the current time in milliseconds for this unique identifier.
Been there & have found several resources to help: Tutorial & Cocoon
The bottom line is you need to ensure child_index is unique for each field. The tutorial I use has child_index: Time.now.to_i to create a truly unique id, consequently allowing you to add as many fields as you want
The best way to do this:
Render fields_for as a partial (passing your form builder object)
When you want to add new field, create ajax_field action
Make ajax_field view have its own form_builder
Both your original & ajax_field forms will call the partial
On front-end, you can use JS to GET new form action & append field to page
I can give you code if you want

A few intermediate Rails3 Questions

I'm building an app called "CourseWork to dig into rails/develop my skills and I have a question about how to structure it. Users have a resource called "CourseGrading" that is able to create categories and belongs to "Course". Each "category" should have a name, a percentage out of 100 and a course_id. I need to add these percentages together and alert users if the total isn't 100 while still saving.
Then the user's generated "categories" should populate an enum_string specific to that user in a resource called "CourseAssignment" which has a name, description, category and finalgrade.
Can anyone give hints or resources for how best to accomplish this? Thanks
You probably want to take a look at Active Record Callbacks. These will allow you to insert some code to be run when creating/validating/updating/deleting models.
You should probably make use of the ActiveRecord validations.
Check out this guide that explains how to write your own custom validator. Your custom validator would run when the form gets submitted, and in it, you would grab the percentage params and do your check. If it's not what you expect, you can just add an error to the form and the validation process will just kick the user back to the form page and display the error.

Triggers/Callbacks in Ruby on Rails

We are creating a system in Ruby on Rails and we want to be able to offer our users a bit of control about notifications and actions that can take place when some pre-defined trigger occurs. In addition, we plan on iterating through imported data and allowing our users to configure some actions and triggers based on that data.
Let me give you a few examples to better clarify:
Trigger - Action
------------------------------------------------------------------------
New Ticket is Created - User receives an e-mail
New Ticket Parsed for Keyword 'evil' - Ticket gets auto-assigned to a
particular group
User Missed 3 Meetings - A ticket is automatically created
Ideally, we would like some of the triggers to be configurable. For instance, the last example would possibly let you configure how many meetings were missed before the action took place.
I was wondering what patterns might help me in doing this event/callback situation in Ruby on Rails. Also, the triggers and actions may be configurable, but they will be predefined; so, should they be hard coded or stored in the database?
Any thoughts would be greatly appreciated. Thanks!
Update 1: After looking at it, I noticed that the badges system on SO is somewhat similar, based on these criteria, I want to do this action. It's slightly different, but I want to be able to easily add new criteria and actions and present them to the users. Any thoughts relating to this?
I think that what you are looking for are the Observers.
In your examples the Observers could handle the first and the third example (but not the second one, since an Observer only observes the object, not interact with it, even though it is technically possible).
Some code to show how I mean:
class TicketObserver < ActiveRecord::Observer
def after_create(ticket)
UserMailer.deliver_new_ticket_notification
end
end
class UserObserver < ActiveRecord::Observer
def after_update(user)
Ticket.new if user.recently_missed_a_meeting and user.missed_meetings > 3
end
end
And then add the observers to environment.rb
config.active_record.observers = :user_observer, :ticket_observer
Of course you will have to fill in the logic for the missed_meetings, but one detail to mention.
Since the after_update will trigger after every time that the user is being updated, the recently_missed_a_meeting attribute is useful. I usually follow the thinking of restful-authentication and have an instance variable that is being set to true everytime I want to trigger that row. That can be done in a callback or in some custom logic depends on how you track the meetings.
And for the second example, I would put it in a before_update callback, perhaps having the keywords in a lookup table to let users update which words that should trigger the move to a specific group.
You should look at the "callback" methods in Rails
For docs see - Callbacks
Your first rule would be implemented via the after_create method.
If you want them to be configurable, I would suggest using a model / table to store the possible actions and doing a lookup within the callback.
If this is high volume, be sure to consider caching the configuration since it would end up doing a db lookup on each callback.
Maybe something like a state-machine can help. Try AASM gem for RoR.

Calling ActiveRecord's #relationship_ids = [1,2,3] saves immediately. Any workarounds?

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

Resources