Rails 3 only update serialized attribute when changed - ruby-on-rails

Using Rails 3 I have a number of model containing serialized attributes. To perform the serialization I'm currently using 3 different techniques - the serialize method; activerecord store; and store configurable gem.
In all cases, when I save a model instance the serialized attribute is updated even if the content is unchanged. This was a surprising discovery particularly when using the store configurable gem as the readme states:
"StoreConfigurable is smart enough to let your parent object know when it changes. It is not dumb either. It will only trigger changes if the values you set are different, are new, or change the configs state."
Am I missing a trick here or if this is the expected behaviour is there a way to override it?

Model with serialized columns being saved all the time is the expected behaviour according to this answer.
I haven't tried "store configurable gem" but it sounds like it should be smart enough to detect that.

I have similar issue so before updating i am resetting all the values and let new values to be entered.

Related

Using PaperTrail, can I opt out of `object_changes` for a particular model or attribute?

This is somewhat related to #837 in that I have a large data column on my models, however I think I may be better served by the opposite of what's proposed in that issue - that is, to maintain the object column but not the object_changes column.
We had been running with no versions.object_changes column. Now that I've added this column, I realized I am writing a lot of data I don't care about for the data column in object_changes - since a tiny change to data causes it to be written out to versions effectively 3x (once in object and twice in object_changes for the before and after).
I don't think skip or ignore is what I want, because I would indeed like the changes to data to produce a new version.
Should I go down the custom version model route? Or what do you recommend?
Some options, in descending order of recommendation (most highly recommended first):
version_limit (Supported) - Save disk space instead by limiting the number of versions you create for a given record, using version_limit. (https://github.com/airblade/paper_trail#2e-limiting-the-number-of-versions-created)
Custom table (Supported) - Custom version model, custom table without object_changes column. Precludes the experimental associations feature (track_associations must be false [the default])
Patch recordable_object_changes, method 1 (Not supported) - Custom version model, but still using the versions table. Override #paper_trail to return a custom child class of RecordTrail which overrides RecordTrail#recordable_object_changes. Overriding these methods breaks your warranty.
Patch recordable_object_changes, method 2 (Not supported) - Override RecordTrail#recordable_object_changes, adding a class-check conditional. Call super for all but the model you want to hack. Overriding this method breaks your warranty.
Custom serializer (Supported, but not for this) - Custom serializer with class-check conditional, and some way of telling whether you're serializing object_changes and not object. Probably a bad idea, seems really hacky.
Finally, I'd be happy to review a PR that adds a new feature, the ability to configure, on a per-model basis, which data should be written to the object_changes column. If you're serious about working on that, and seeing it through to the finish, please open a new issue so we can discuss it further. There are a few different designs that could work.
Update, 2019: We now have object_changes_adapter It's only for expert users, and probably not my top recommendation.

Best practices regarding per-user settings and predefining options

I want to save settings for my users and some of them would be one out of a predefined list! Using https://github.com/ledermann/rails-settings ATM.
The setting for f.e. weight_unit would be out of [:kg, :lb].
I don't really want to hardcode that stuff into controller or view code.
It's kind of a common functionality, so I was wondering: Did anyone come up with some way of abstracting that business into class constants or the database in a DRY fashion?
Usually, when I have to store some not important information which I don't care to query individually, I store them on a serialized column.
In your case you could create a new column in your users table (for example call it "settings").
After that you add to user model
serialize :settings, Hash
from this moment you can put whatever you like into settings, for example
user.settings = {:weight_unit => :kg, :other_setting1 => 'foo', :other_setting2 => 'bar'}
and saving with user.save you will get, in settings column, the serialized data.
Rails does also de-serialize it so after fetching a user's record, calling user.settings, you will get all saved settings for the user.
To get more information on serialize() refer to docs: http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
UPDATE1
To ensure that settings are in the predefined list you can use validations on your user model.
UPDATE2
Usually, if there are some pre-defined values it's a good habit to store them in a constant inside the related model, in this way you have access to them from model (inside and outside). Acceptable values does not change by instance so it makes sense to share them between all. An example which is more valuable than any word. Defining in your User model:
ALLOWED_SETTINGS = {:weight_unit => [:kg, :lb],
:eyes_color => [:green, :blue, :brows, :black],
:hair_length => [:short, :long]}
you can use it BOTH
outside the model itself, doing
User::ALLOWED_SETTINGS
inside your model (in validations, instance methods or wherever you want) using:
ALLOWED_SETTINGS
Based on your question, it sounds like these are more configuration options that a particular user will choose from that may be quite static, rather than dynamic in nature in that the options can change over time. For example, I doubt you'll be adding various other weight_units other than :kg and :lb, but it's possible I'm misreading your question.
If I am reading this correctly, I would recommend (and have used) a yml file in the config/ directory for values such as this. The yml file is accessible app wide and all your "settings" could live in one file. These could then be loaded into your models as constants, and serialized as #SDp suggests. However, I tend to err on the side of caution, especially when thinking that perhaps these "common values" may want to be queried some day, so I would prefer to have each of these as a column on a table rather than a single serialized value. The overhead isn't that much more, and you would gain a lot of additional built-in benefits from Rails having them be individual columns.
That said, I have personally used hstore with Postgres with great success, doing just what you are describing. However, the reason I chose to use an hstore over individual columns was because I was storing multiple different demographics, in which all of the demographics could change over time (e.g. some keys could be added, and more importantly, some keys could be removed.) It sounds like in your case it's highly unlikely you'll be removing keys as these are basic traits, but again, I could be wrong.
TL;DR - I feel that unless you have a compelling reason (such as regularly adding and/or removing keys/settings), these should be individual columns on a database table. If you strongly feel these should be stored in the database serialized, and you're using Postgres, check out hstore.
If you are using PostgreSQL, I think you can watch to HStore with Rails 4 + this gem https://github.com/devmynd/hstore_accessor

Rails - best practice to store key/value hash

Here's my challenge. I have a key/value set that I want to tie to a model. These are my specific requirements:
I want the hash to be stored as a serialized JSON object in the model's table instead of in a separate table
I want to be able to pre-define the valid keys within the model itself
I want to be able to set a strong type for each key and automatically perform validations. I don't want to have to write validation functions for each individual attribute unless it needs a validation out of the basic data type scope.
I would LOVE to be able to magically access the attributes inside a form generator (f.input :my_key) and have the form generator recognize that :my_key is of type :boolean and create a checkbox instead of a generic text input. The same for other data types.
There are a few different ways to solve this problem, and lots of opinions for both. I read over this answer from 5 years ago:
Best approach to save user preferences?
It seems that many/most of those plugins have been abandoned. Anything else come out in the last 5 years that matches my criteria?
Your question is a bit open-ended, but as far as I can see your needs, they should be met with using Hashie gem.

How to design a database in rails where all tables can be filtered by a global setting? Default global scope?

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...

Mongoid and Postgres Scaffolding/Relationships

I have a need for a certain model to contain a reference to a document. Most of the model could be stored in postgres. The model is for a "level" in a game. I'd like to store the level data itself inside of a document, which makes more sense than making a complex tree in sql.
I am able to use postgres with mongoid installed; however, after installing the mongoid gem I seem to only be able to scaffold mongoid (non active record) documents.
The problem is that I have references to other tables, and I don't neccesarily know how to link that up within a mongoid model.
Questions:
How can I force scaffolding to occur with active record instead of mongoid or vice versa. Edit: partly answered here: Using Active Record generators after Mongoid installation? (2nd answer works, but I don't know how to go back and forth easily)
Is there an easy way to reference a document from an active record model (I know the documentation said don't mix them, but it is ideal for what I am trying to do).
If it is not possible to mix them, then how should I make a document be referenced from a postgres/active record table. In other words how can I get both pieces of data at the same time?
Thanks!
Regarding your first question, the ideal solution would be something along the lines of the first answer in the referenced post. However, instead of a generating a migration, generate a model instead. So when you want an Active Record model simply run:
rails g active_record:model
As for your second and third questions, to associate an Active Record model with a Mongoid document simply store the ObjectId as a string in the model. Then, when you get retrieve a record make a new ObjectId out of the string and use that to query for the related document.
You can create object ids out of the strings like this:
BSON::ObjectId.from_string("object_id_string")
There isn't really an easy way to easily follow intra-orm relations when mixing and matching between ActiveRecord and Mongoid though so I'm afraid that will have to be done via Ruby code.
The models you define in rails either extend one ORM's base class or the other and they don't know about one another. There may be projects out there that act as a layer on top of these ORMs but I am not familiar with any that exist at the moment.

Resources