I'm wondering what the best way is to store user settings? For a web 2.0 app I want users to be able to select certain settings. At the moment is it only when to receive email notifications.
The easiest way would be to just create a Model "Settings" and have a column for every setting and then have a 1-1 relationship with users.
But is there a pattern to solve this better? Is it maybe better to store the info in the user table itself? Or should I use a table with "settings_name" and "settings_value" to be completely open about the type of settings stored there (without having to run any migrations when adding options)?
What is your opinion?
Thanks
If you use PostgreSQL, the best solution is to use https://github.com/diogob/activerecord-postgres-hstore/. It's a simple, fast and reliable way to store hashes in the database. Since it's not just a serialised text field, you can search on it also, and you don't need to create a new table, as in HasEasy.
def User
serialize :preferences, ActiveRecord::Coders::Hstore
end
user = User.create preferences: { theme: "navy" }
user.preferences['theme']
The "open" table approach makes it difficult to model with AR, since you have to worry about the types of the data (boolean, int, string, etc). I've always added prefs as columns on the users table, and then move them out to a user_preferences table if there are "too many" of them. It's simple, and it's easy to work with.
If the user settings are not meant to be findable (via a User.find_by_x_preference, e.g.) you could also store them in a serialized column as a hash. This is the use case described in the rails docs (http://www.railsbrain.com/api/rails-2.3.2/doc/index.html?a=M002334&name=serialize#), actually.
class User < ActiveRecord::Base
serialize :preferences
end
u = User.new
u.preferences = {:favorite_color => "green", :favorite_book => "Moby Dick"}
We use the helpful plugin called HasEasy. It stores the data in a vertical table, but allows you to add validations, pre/post storage processing, types, etc.
Rails 6 supports ActiveRecordStore that can be used to solve this problem. The gem that improves ActiveRecordStore, by adding type definition, is activerecord-typedstore.
Define attributes in your model:
class User < ApplicationRecord
typed_store :settings do |s|
s.boolean :public, default: false, null: false
s.string :email
s.datetime :publish_at
s.integer :age, null: false
end
end
And use can use it:
shop = Shop.new(email: 'george#cyclim.se')
shop.public? # => false
shop.email # => 'george#cyclim.se'
shop.published_at # => nil
Related
I'd like to incorporate a step to check for an existing relation object as part of my model creation/form submission process. For example, say I have a Paper model that has_and_belongs_to_many :authors. On my "Create Paper" form, I'd like to have a authors_attributes field for :name, and then, in my create method, I'd like to first look up whether this author exists in the "database"; if so, then add that author to the paper's authors, if not, perform the normal authors_attributes steps of initializing a new author.
Basically, I'd like to do something like:
# override authors_attributes
def authors_attributes(attrs)
attrs.map!{ |attr| Author.where(attr).first_or_initialize.attributes }
super(attrs)
end
But this doesn't work for a number of reasons (it messes up Mongoid's definition of the method, and you can't include an id in the _attributes unless it's already registered with the model).
I know a preferred way of handling these types of situations is to use a "Form Object" (e.g., with Virtus). However, I'm somewhat opposed to this pattern because it requires duplicating field definitions and validations (at least as I understand it).
Is there a simple way to handle this kind of behavior? I feel like it must be a common situation, so I must be missing something...
The way I've approached this problem in the past is to allow existing records to be selected from some sort of pick list (either a search dialog for large reference tables or a select box for smaller ones). Included in the dialog or dropdown is a way to create a new reference instead of picking one of the existing items.
With that approach, you can detect whether the record already exists or needs to be created. It avoids the need for the first_or_initialize since the user's intent should be clear from what is submitted to the controller.
This approach struggles when users don't want to take the time to find what they want in the list though. If a validation error occurs, you can display something friendly for the user like, "Did you mean to pick [already existing record]?" That might help some as well.
If I have a model Paper:
class Paper
include Mongoid::Document
embeds_many :authors
accepts_nested_attributes_for :authors
field :title, type: String
end
And a model Author embedded in Paper:
class Author
include Mongoid::Document
embedded_in :paper, inverse_of: :authors
field :name, type: String
end
I can do this in the console:
> paper = Paper.create(title: "My Paper")
> paper.authors_attributes = [ {name: "Raviolicode"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Raviolicode">]
> paper.authors_attributes = [ {id: paper.authors.first, name: "Lucia"}, {name: "Kardeiz"} ]
> paper.authors #=> [#<Author _id: 531cd73331302ea603000000, name: "Lucia">, #<Author _id: 531cd95931302ea603010000, name: "Kardeiz">]
As you can see, I can update and add authors in the same authors_attributes hash.
For more information see Mongoid nested_attributes article
I followed the suggestion of the accepted answer for this question and implemented a reject_if guard on the accepts_nested_attributes_for statement like:
accepts_nested_attributes_for :authors, reject_if: :check_author
def check_author(attrs)
if existing = Author.where(label: attrs['label']).first
self.authors << existing
true
else
false
end
end
This still seems like a hack, but it works in Mongoid as well...
So in my rails app, I'm creating a table of users, and in that table I have columns such as "email, password, and image". I want to add a concrete list of nationalities that a user can be associated with.
So, I created a user model:
rails g model User email password image:string
Now I want an array of nationalities... so instead of creating an array, I created a separate model for nationalities and want to associate it to my users. Assuming one can have multiple nationalities, I set it up like this:
rails g model Nationality name:string
So in user.rb I wrote:
has_many :nationalities
and in nationality.rb I wrote:
belongs_to :user
When a user signs up, I will have a list for them to choose from: American, Canadian, French, etc...
My gut tells me this might be an antipattern since a user won't ever be adding new nationalities to the list... only selecting, but an admin can always add more nationalities.
I'm not getting any errors, but it isn't working in my favor either.
Did I set it up correctly, or should I just use an array and store it in the user's table under a column called "nationalities"?
edit: Oops, I didn't address the part of having multiple nationalities. You may want to consider using acts_as_taggable_on
https://github.com/mbleigh/acts-as-taggable-on
It nicely handles the many-to-many behavior of tagging with some built in conveniences.
I think it's fine to use some kind of global array (in a namespace) for Nationalities for now, because as you note correctly, the kind of Nationalities will not change.
However, I think things will be a little more complicated than a straight-up Array...You say you will give choices such as American, Canadian, French...and that's fine, but what if the context of a view calls for using U.S., Canada, France instead? Then your Array becomes a Hash...I doubt it'll get complicated enough to require a full fledged Rails model. A simple Hash should cover most of those use cases.
I've found it helpful in such situations to create a separate Class or Module. For example, with Gender, it's most definitely not worth creating a separate DB table...and yet, even if all I store in a user's gender column is "M", "F", "O" (for other, or maybe not specified), I'll create a Gender module that can handle that domain logic:
class Gender
# better to make this private so that
# only the factory constructor is exposed
def initialize(g)
#g = g
end
def to_s # and equivalent methods for serializing Gender into a M/F for database
return #g.to_s
end
def pronoun
case #g
when :M
'he'
when :F
'she'
else
'they'
end
end
def Gender(str)
case str
when /^(?:m|male|man|boy)$/i
Gender.new(:M)
when /^(?:f|female|woman|girl)$/i
Gender.new(:F)
else
Gender.new(:O)
end
end
This is really more a database design question than a Rails question, but I would just have the Nationality table serve as the source for a dropdown, but the User table would simply have a nationality column. I don't see the need for the extra join. Of course, the values in the nationality column would come from user selections from that dropdown populated by the Nationality table.
Still, your needs may vary. Stick with the join if multiple nationalities are a requirement. As Knuth said, "Premature optimization is the root of all evil."
Are you using Rails 4? Is Postgresql your DB? If so this combination allows you to store arrays on your user model.
Basically you would modify your migration to look like this:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name # or whatever attributes you've already defined
t.string :nationalities, array: true, default: []
t.timestamps
end
end
end
Your ActiveRecord model would contain this:
class User < ActiveRecord::Base
attr_accessible :nationalities
end
This would allow you to create the nationalities list like so:
user = User.create(name: 'Bob', nationalities: ['English', 'Australian', 'French'])
And obviously extract them like:
user.nationalities
=> ['English', 'Australian', 'French']
Still, the decision about having an association or a column on the user goes beyond the ease of creating the user with a nationality in a form. The real question is "what role will the nationality play in the app"? Meaning, does the nationality have additional data that would warrant it being it's own model? Or, can it all just sit in the user table?
If the nationality has as much functionality as a user name, than it can stay on the user. However, if nationality can have other attributes on it such as 'date_aquired' than it may have to be it's own model.
I am playing around with Mongoid (NoMySQL Database), DEVISE and CANACN
I have read the manual (https://github.com/ryanb/cancan/wiki/Role-Based-Authorization),
but I dont wanna use the "roleMask"-calculation. I wanna use an Array (or Hash) -field instead, containing the roles of a user.
class User
include Mongoid::Document
field :email, :type => String, :null => false
field :roles, :type => Array, default: -> { ['User'] if new_record?}
Is this possible in any way? And when yes, how? :-)
many thanks in advance
Cancan doesn't really care how your roles work - your ability file is pure ruby so the logic is entirely up to you. For example you might have this in your ability:
can :manage, Product
If you want to restrict this to users whose roles array contains a certain value then you could do
if user.roles.include?('Admin')
can :manage, Product
end
Since your ability is just a ruby class you can do pretty much anything that you can express in ruby.
There are multiple ways of dealing with inheritance, two are outlined in the cancan wiki.
One way involves changing how you check for a role: if you had an admin? method, then instead of just checking for the presence of the admin role it would also check for the presence of any other roles that should inherit all of that access.
Another way is to split your ability file into methods named after the role, for example all the statements for the editor role would be in the editor method, the manager method contains all the statements for the manager role etc. If for example managers need to inherit the access an editor has then call the editor method from manager.
Please help a newbie to choose the best way to implement inheritance in RoR3. I have:
-Person (address fields, birthdate, etc.)
-Player, inherits from Person (position, shoe_size, etc.)
-Goalkeeper, inherits from Player (other specific fields related to this role)
I think that Single Table Inheritance is a bad solution, because there will be a lot of null fields in the table created. What is the best way to do this? Use polymorphic associations (with has_one?)? Use belongs_to/has_one (but then how to show in the Player views the fields of Person too?)? Don't implement inheritance? Other solutions?
While I think STI is probably the approach I would use for this, one other possibility, if you want to avoid a lot of NULL attributes, is to add a column other_attributes to your Person model that will store a Hash of attributes. To do this, add a text column to the people table:
def self.up
add_column :people, :other_attributes, :text
end
Then make sure the attribute is serialized in the model. And you may want to write a wrapper to make sure it's initialized as an empty Hash when you use it:
class Person < ActiveRecord::Base
serialize :other_attributes
...
def other_attributes
write_attribute(:other_attributes, {}) unless read_attribute(:other_attributes)
read_attribute(:other_attributes)
end
end
Then you can use the attribute as follows:
p = Person.new(...)
p.other_attributes #=> {}
pl = Player.new(...)
pl.other_attributes["position"] = "forward"
pl.other_attributes #=> {"position" => "forward"}
One caveat with this approach is that you should use strings as keys when retrieving data from other_attributes, as the keys will always be strings when the Hash is retrieved from the database.
I suggest STI. An alternative solution is to use a document store like mongodb, or use the activerecord store http://api.rubyonrails.org/classes/ActiveRecord/Store.html. If you have a postgress database look at his HStore column http://rubygems.org/gems/activerecord-postgres-hstore.
Another option is PostgreSQL table inheritance. http://www.postgresql.org/docs/8.1/static/ddl-inherit.html
I'm just learning ruby on rails and I have a table of user roles (Owner, Admin, and User). There are going to be places in the code where I need to check the user's role and show different options. Does anyone know how to do this without resorting to magic numbers or other ugly methods?
In ASP.Net web apps I've worked on I've seen this done through the use of enumerated types:
public enum UserRole { Owner = 1, Admin = 2, User = 3 }
// ...
if (user.Role == UserRole.Admin)
// Show special admin options
Each different role in the database is reflected as an enumerated type with a value set to the ID of that role in the database. That doesn't seem like a very good solution because it depends on knowledge of database that may change. Even if it is the proper way to handle something like this, I don't know how to use enumerated types in rails.
I would appreciate any insight into this matter.
Ruby itself does not have an enumerated type, but this site shows a method http://www.rubyfleebie.com/enumerations-and-ruby/
You could make something like this in your User model:
#constants
OWNER = 1
ADMIN = 2
USER = 3
def is_owner?
self.role == OWNER
end
def is_admin?
self.role == ADMIN
end
def is_user?
self.role == USER
end
Could the functionality added in Rails 4.1, be what you are looking for ?
http://coherence.io/blog/2013/12/17/whats-new-in-rails-4-1.html
Copy from blog post:
class Bug < ActiveRecord::Base
# Relevant schema change looks like this:
#
# create_table :bugs do |t|
# t.column :status, :integer, default: 0 # defaults to the first value (i.e. :unverified)
# end
enum status: [ :unverified, :confirmed, :assigned, :in_progress, :resolved, :rejected, :reopened ]
...
Bug.resolved # => a scope to find all resolved bugs
bug.resolved? # => check if bug has the status resolved
bug.resolved! # => update! the bug with status set to resolved
bug.status # => a string describing the bug's status
bug.status = :resolved # => set the bug's status to resolved
This seems like a really good case for using my classy_enum gem. It essentially allows you to define a fixed set of options, where each one is a class with behavior and properties specific to it. It helps cut down on all the conditional logic that tends to get scattered throughout the application.
For example, If you are doing things like this:
class User < ActiveRecord::Base
def options
if user.is_admin?
[...admin options...]
else
[...non admin options...]
end
end
end
Then calling as: user.options somewhere else...
classy_enum allows you to move that logic to a separate place and have the same functionality with no conditional logic:
class User < ActiveRecord::Base
classy_enum_attr :role
delegate :options, :to => :role
end
The README has a working example and describes the gem in detail.
I prefer to use the aptly named Authorization plugin for situations like this.
This will let you
permit "role"
to restrict access to roles, and
permit? "role"
to simply test for access. Both of these delegate to User#has_role?(role).
Don't feel like you have to use their ObjectRoles implementation. You can use the Hardwired roles and then implement your own User#has_role?(role) method to use your existing schema.
Just starting to learn Rails (from C#), and had this exact same question. It seems that Rails doesn't really have enums because the philosophy is different. I would use tons of enums to try to organize all the details in a C# project, but maybe since Rails handles so much for you they aren't that important. It's not really an answer, just an observation.
There's a enum plugin on rubyforge so you do:
t.column :severity, :enum, :limit => [:low, :medium, :high, :critical]
It's pretty ugly to use :limit attribute to pass parameters, but it's a more standardized way.
To install just do:
script/plugin install svn://rubyforge.org/var/svn/enum-column/plugins/enum-column
it currenctly works with Rails 2.2.2 or later.
Rubyforge link: www.rubyforge.org/projects/enum-column/