I am attempting to use the ice_cube gem to set up a schedule for recurring shifts. What I am struggling with is the recurring events are not real instances of my model with an ID; I need to be able to reference each event to associate it with a shift_confirmation method (detailing things like the actual end time vs. the booked end time, whether the shift has been paid, etc.).
Where I'm struggling is that if I add an actual instance of my model, as opposed to a recurring instance, I will end up with both - the instance I can refer to with an ID, and also a recurring instance.
I have considered whether I can add an IceCube exception that removes a shift from the recurrence schedule each time a user wants to edit it (e.g. to mark it complete, paid, etc.), and create an instance of my model, but this seems very unclean - there must be a better way.
Is there any way to refer definitively to an specific event in an ice_cube recurring schedule, so that I can do things like mark it paid (which would be in an associated model)?
When you create an schedule, the schedule has an idea, probably you can use this in your structure.
I viewed this post, because i've a similar problem.
I've an table activities, with start-, end datum and duration and an related table schedule with the reference 'activity_id'. I didn't want to store the ice-cube to_yaml string, because I want the option to add 'closing dates' in the future on a central place, without parsing every record again.
If you show your structure, maybe I can be of some help.
Regards, sander
Check out the schedulable gem, it lets you persist event_occurrences in the database and let's your "schedule" edit only future occurrences.
Related
We are exploring using the flipper gem (https://github.com/jnunemaker/flipper) to gate who sees new features. In one of our first tests, we want to show a specific feature to only the first X users that see a banner promoting it.
We looked at using a percentage, but the business is very specific on the number, and also wants to reach that number right away, then disable the feature for all other users, without disabling it for those that saw it first. Using a percentage, we weren't able to see a way to ensure the correct number would see it, and that everyone of the first x would see it.
Inside the gates/actor.rb, there is this:
enabled_actor_ids = value
which implies we could get the list of enabled ids, and perform a count on that, but we couldn't find whether or where that list may be exposed.
Since we are using the AR adapter as a trial, we instead created a scope on an actor object that joins to the flipper_gates table, but this feels extremely fragile and getting very much into the inner workings of the gem.
Any advice is greatly appreciated.
Nowadays you can do Flipper[:some_feature].actors_value.size, assuming you've configured your default flipper instance with Flipper.configure.
https://github.com/jnunemaker/flipper/blob/196946c63aee1eaa09fa25e945cdbff896fe71e5/lib/flipper/feature.rb#L258-L260
You should be able to accomplish this by programmatically turning the feature on for Individual Actors until an upper limit is reached.
IMPORTANT NOTE: according to the documentation:
The individual actor gate is typically not designed for hundreds or
thousands of actors to be enabled. This is an explicit choice to make
it easier to batch load data from the adapters instead of performing
individual checks for actors over and over. If you need to enable
something for more than 20 individual people, I would recommend using
a group.
Now that we've agreed that we want to move forward with this anyways.. Let's talk about implementation.
Enabling the feature for an actor
The first thing you need to do is to ensure that the actor (probably a User) responds to flipper_id and that the flipper_id is unique for every actor. Once that is set up, you should be able to simply do enable the feature for a user when they see the banner like this:
flipper[:stats].enable_actor user
Counting actors enrolled in a feature
Now, in order to determine if we should enable the feature for a user, we need to determine how many users have been enrolled in the feature.
To do this we can query the Gate directly:
Flipper::Adapters::ActiveRecord::Gate.where(
feature_key: "stats",
key: "actors"
).count
This will return a count of the number of actors enrolled in a feature.
How do we know that works?
Well, let's take a look at the gem.
flipper[:stats].enable_actor actually calls Feature#enable_actor with the user we passed in earlier (that responds to flipper_id) being passed in as the actor.
Next, Feature#enable_actor passes the actor into Types::Actor.wrap which creates a new instance of Types::Actor which checks to make sure the actor isn't nil and that it has a flipper_id and then sets two instance variables, thing which is set to the actor, and value which is set to the flipper_id of the actor.
Now that we have an instance of Types::Actor, we pass it into Feature#enable which looks up the gate which in our case would be a Gates::Actor instance. Finally we call enable on the adaptor (which in your case is ActiveRecord).
In Adapters::ActiveRecord.enable we first look at gate.data_type which in our case, is :set. From there we do:
#gate_class.create! do |g|
g.feature_key = feature.key
g.key = gate.key
g.value = thing.value.to_s
end
Where, as mentioned earlier, thing.value is the flipper_id. Bingo! #gate_class is the active record class responsible for the gates table and the default table name is "flipper_gates".
Now we know exactly what to query to get a count of the actors enrolled in the feature!
number_of_actors_enrolled_in_stats_feature = Flipper::Adapters::ActiveRecord::Gate.where(
feature_key: "stats",
key: "actors"
).count
long time reader first time poster.
I recently started using ruby on rails so I am still very new to the environment (even though I have completed a few guides) so be gentle please.
What I want to do is create a sort of archive table of another table that the user can access at any time(via a different link on the website).
So for example, if I have the "users" table, I want to be able to archive old users but still give the option for someone to go and view them.
Basically, it will sort of have to delete the user from the initial table, and save his/her info in to the archived_users table.
Thank you for your time.
I figured my comment was more of an answer, so posting it here and adding more info
In this situation you're better off adding some sort if "active" flag to the users table, which you can flip on or off as needed. That way you don't need to worry about dealing with yet another model class, and you can reuse all the same view and controller structures. In your views, you can then simply "hide" any inactive users (and maybe only show inactive folks if the logged in user is an admin...etc).
You also have the freedom to include other meta data such as "deactivated on" (time stamp) for example.
Long story short, if you're concerned about performance, with proper indexing (and partitioning if necessary), you shouldn't really need to create a separate archive table.
The only reason I can think of to do this is if you're dealing with billions upon billions of records, and/or growing by an insane amount (which is probably not your case).
The best way to do this is probably to add a column called deleted on the original Users table. You can then filter out the old users in normal circumstances (preferably using a default scope) but allow them to be seen/queried when needed.
Papertrail might work for you.
It creates a "versions" table and logs create/update/destroy events for any class which includes has_paper_trail. For example:
class User < ActiveRecord::Base
has_paper_trail
end
deleted_users = Papertrail::Version.where(item_type: User, event: "destroy")
deleted_users.last.reify.name # assuming the users table has a 'name' column
I am building a rails app and the data should be reset every "season" but still kept. In other words, the only data retrieved from any table should be for the current season but if you want to access previous seasons, you can.
We basically need to have multiple instances of the entire database, one for each season.
The clients idea was to export the database at the end of the season and save it, then start fresh. The problem with this is that we can't look at all of the data at once.
The only idea I have is to add a season_id column to every model. But in this scenario, every query would need to have where(season_id: CURRENT_SEASON). Should I just make this a default scope for every model?
Is there a good way to do this?
If you want all the data in a single database, then you'll have to filter it, so you're on the right track. This is totally fine, as data is filtered all the time anyway so it's not a big deal. Also, what you're describing sounds very similar to marking data as archived (where anything not in the current season is essentially archived), something that is very commonly done and usually accomplished (I believe) via setting a boolean flag on every record to true or false in order to hide it, or some equivalent method.
You'll probably want a scope or default_scope, where the main downside of a default_scope is that you must use .unscoped in all places where you want to access data outside of the current season, whereas not using a default scope means you must specify the scope on every call. Default scopes can also seem to get applied in funny places from time to time, and in my experience I prefer to always be explicit about the scopes I'm using (i.e. I therefore never use default_scope), but this is more of a personal preference.
In terms of how to design the database you can either add the boolean flag for every record that tells whether or not that data is in the current season, or as you noted you can include a season_id that will be checked against the current season ID and filter it that way. Either way, a scope of some sort would be a good way to do it.
If using a simple boolean, then either at the end of the current season or the start of the new season, you would have to go and mark any current season records as no longer current. This may require a rake task or something similar to make this convenient, but adds a small amount of maintenance.
If using a season_id plus a constant in the code to indicate which season is current (perhaps via a config file) it would be easier to mark things as the current season since no DB updates will be required from season to season.
[Disclaimer: I'm not familiar with Ruby so I'll just comment from the database perspective.]
The problem with this is that we can't look at all of the data at once.
If you need to keep the old versions accessible, then you should keep them in the same database.
Designing "versioned" (or "temporal" or "historized") data model is something of a black art - let me know how your model looks like now and I might have some suggestions how to "version" it. Things can get especially complicated when handling connections between versioned objects.
In the meantime, take a look at this post, for an example of one such model (unrelated to your domain, but hopefully providing some ideas).
Alternatively, you could try using a DBMS-specific mechanism such as Oracle's flashback query, but this is obviously not available to everybody and may not be suitable for keeping the permanent history...
I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.
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.