I have a rails app that like so many rails apps, has users. In my user model i have password and salt columns, among others. Is there any way i can protect these from appearing when i do for example debug #user or when i render JSON?
Of course i could just make sure to omit these every time i use it, but is there a way i can make really sure that they don't show up anywhere?
Maybe they could be private fields? They are only needed in my User model and my Session controller, but i could make a method in User that compares a given password to the correct one and then only have the variables accessible in the ´User´ module. Would this help, or would they still be rendered in those two places ( and others )?
Very good question, there are several things to consider:
Are you looking to "privatise" the values in the model or in the Rails Core?
Will you need to access these attributes in any specific circumstances? (IE are they always private?)
How will the private methods be populated? Will they ever change?
I have to be honest in saying I don't know the answer. Since I'm interested, I did some research. Here are some resources:
Is there a way to make Rails ActiveRecord attributes private?
Hiding an attribute in an ActiveRecord model
ActiveRecord protects your privates
The consensus seems to be that if you take the Rails model, and apply the logic that every time you populate it, its attributes become instance methods of the, you can begin to privatise those methods within the model itself.
For example...
#app/models/user.rb
class User < ActiveRecord::Base
private :password, :password=
private :salt, :salt=
end
This seems to give you the ability to make certain methods private in your model, which will answer half your question. However, it still leaves the possibility of ActiveRecord pulling the record each time, which could be a danger.
I had a look into this, and found that there are certain ways you can manipulate ActiveRecord to prevent it pulling unwanted data:
ActiveRecord : Hide column while returning object
This resource recommends the use of active_record_serializers. This appears specifically for JSON, but is more along the right track (IE the ability to define which data we return from ActiveRecord queries).
#serializer
class UserSerializer < ActiveModel::Serializer
attributes :username, :created_at
end
#controller
render json: #user, serializer: UserSerializer
There was another suggestion of ActiveRecord Lazy Attributes - stipulating to ActiveRecord which attributes to load:
#app/models/user.rb
class User < ActiveRecord::Base
attr_lazy :data
end
Finally, you always have good ol' default_scope:
#app/models/user.rb
class User < ActiveRecord::Base
default_scope select([:id, :username])
end
Related
Motivation
The motivation was that I want to embed the serialization of any model that have been included in a Relation chain. What I've done works at the relation level but if I get one record, the serialization can't take advantage of what I've done.
What I've achieved so far
Basically what I'm doing is using the method includes_values of the class ActiveRecord::Relation, which simply tells me what things have been included so far. I.e
> Appointment.includes(:patient).includes(:slot).includes_values
=> [:patient, :slot]
To take advantage of this, I'm overwriting the as_json method at the ActiveRecord::Relation level, with this initializer:
# config/initializers/active_record_patches.rb
module ActiveRecord
class Relation
def as_json(**options)
super(options.merge(include: includes_values)) # I could precondition this behaviour with a config
end
end
end
What it does is to add for me the option include in the as_json method of the relation.
So, the old chain:
Appointment.includes(:patient).includes(:slot).as_json(include: [:patient, :slot])
can be wrote now without the last include:
Appointment.includes(:patient).includes(:slot).as_json
obtaining the same results (the Patient and Slot models are embedded in the generated hash).
THE PROBLEM
The problem is that because the method includes_values is of the class ActiveRecord::Relation, I can't use it at the record level to know if a call to includes have been done.
So currently, when I get a record from such queries, and call as_json on it, I don't get the embedded models.
And the actual problem is to answer:
how to know the included models in the query chain that retrieved the
current record, given that it happened?
If I could answer this question, then I could overwrite the as_json method in my own Models with:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
extend Associations
def as_json(**options)
super(options.merge(include: included_models_in_the_query_that_retrieved_me_as_a_record))
end
end
One Idea
One Idea I have is to overwrite the includes somewhere (could be in my initializer overwriting directly the ActiveRecord::Relation class, or my ApplicationRecord class). But once I'm there, I don't find an easy way to "stamp" arbitrary information in the Records produced by the relation.
This solution feels quite clumsy and there might be better options out there.
class ApplicationRecord < ActiveRecord::Base
def as_json(**options)
loaded_associations = _reflections.each_value
.select { |reflection| association(reflection.name).loaded? }
.map(&:name)
super(options.merge(include: loaded_associations))
end
end
Note that this only loads 1st level associations. If Appointment.includes(patient: :person) then only :patient will be returned since :person is nested. If you plan on making the thing recursive beware of circular loaded associations.
Worth pointing out is that you currently merge include: ... over the provided options. Giving a user no choice to use other include options. I recommend using reverse_merge instead. Or swap the placements around {includes: ...}.merge(options).
I have a scenario where I generate reports from certain ActiveRecord models.
I have multiple roles in the application. Depending on the roles, I want to show or hide certain columns. The thing is as the number of screens/pages increase, keeping a track of these can be become a nightmare.
Is there a way in Rails, where, I can stop returning values for certain columns depending on certain conditions. For e.g. I will the object returned from a ActiveRecord.Where will have data for some columns masked depending on User's role.
You can do that using active record select method. Select only those attributes which current user role can access and pass to view.
For this you can create array of accessible feilds for paticular role in your initializer. For this create a initializer.rb file under config/initializers/. Add code something like:
ADMIN = ['feild1', 'feild2'..., 'feild10']
MANAGER = ['feild1', 'feild2'..., 'feild5']
USER = ['feild1', 'feild2', 'feild3']
in your action write code something like :
Model.select(eval(current_user.role.upcase))
In view you need to check if attribute exist in your retured activerecord or not. Otherview you will get ActiveModel::MissingAttributeError: for this:
object.has_attribute? 'att_name'
Or you can rescue it with nil
object.att_name rescue nil
I literally just wrote an answer about this - you'll probably benefit from it.
Model
It seems that if you want to return specific ActiveRecord data, there are certain ways to limit the attributes the class builds. More specifically, you can make certain methods "private" - preventing your model from returning them.
Although I'm not 100% sure on this, I can say that there are two "levels" to your question -- the database data & the model's construction. Although I don't have anything for the ActiveRecord side of things, the model can "privatize" certain attributes, preventing them from being available in other parts of your app.
A Rails model is a class - populated with attributes. This means you should be able to control which attributes are available by the Role your user is part of:
#app/models/role.rb
class Role < ActiveRecord::Base
#columns id | name | attributes | created_at | updated_at
#"attributes" can be used to assign an array
has_many :users, inverse_of: :role
end
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role, inverse_of: :users
role.attributes.each do |attr|
private attr.to_sym
end
end
This will override the attributes pulled from the db, allowing you to determine which ones are available.
Of course, a very rudimentary procedure.
--
ActiveRecord
The best way around this will be to use ActiveRecord to specifically select the attributes / columns you want. To do this, I'm not sure of the absolute best way, but perhaps using a default_scope would be beneficial:
#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :role
def attributes
case role_id
when "1"
attrs = []
when "2"
attrs = []
when "3"
attrs = []
end
end
default_scope (select: attributes)
end
Again, pretty rudimentary. I'd be interested in seeing a more integrated way of doing this.
In general, I have a website which needs to have complex registration process. And in that registration process I need to include 4 tables from database.
Now... I cannot validate one by one model and to enter 4 of them in database. Is there a way to make common points of all those tables in one model?
Let's say:
User model has columns: username, name, etc.
Package model has: type, account_number
etc
And in registration process I need to include username, name, account_number and to validate them. How to do that?
Without seeing your model structure, this is just speculation, but here goes:
--
Virtual Attributes
In your User model, you can use attr_accessor to create a set of virtual attributes - which basically mean you can create a series of setter / getter methods in your User model.
Although I don't think this will help you directly, it should give you an idea as to how you can create single-model validation:
#app/models/user.rb
Class User < ActiveRecord::Base
attr_accessor :new, :values, :to, :validate
validates, :new, :values, :to, :validate, presence: true
end
This will allow you to create the attributes in the User model - and although they won't be saved, you can then use them to validate against
attr_accessor
To give you a quick overview of this method, you first need to remember that Rails is just a collection of modules and classes.
This means that every model you load is just a class which has been populated with a series of getter / setter methods. These methods are defined by ActiveRecord from your data table columns, and are why you can call #user.attribute
The magic is that if you use attr_accessor, you'll basically create your own attributes in your User model - which won't be saved in the database, but will be treated like the other attributes your objects have, allowing you to validate them
Because your registration process seems to be complex, I would go even futher as virtual attributes and use Form Objects
7 Patterns to Refactor Fat ActiveRecord Models
LA Ruby Conference 2013 Refactoring Fat Models
ActiveModel Form Objects
I understand that you multistep registration. You shouldn't create 4 models only because your view pages needs it. You should:
remove validation from User model and add validation on each form
create 4 different forms (for example extends by ActiveModel or user gem reform)
add validation to each form
after form.valid? save part of user info to #user object
Thats all.
I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)
It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.
If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.
Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end
I am always reading about keeping Controllers thin and doing all logic in models. While this makes senses to me for interacting with databases, what about situations where there is no need to for database interactions?
I have a fairly complex module in my app that interact with several different third party APIs. I use ajax calls to my controller, where all the data is gathered from the APIs and then organized. Then it is displayed via the corresponding .js.erb or .html.erb files.
Is this the proper way to handle this kind of situation? I'm new to rails and don't want to get into habit of doing things wrong.
Models are not just for dealing with database, but for working with data in principle.
As far as we don't know what situations you mean I can just present some situations.
Ajax call for big Math calculating. It is not touching database and even it can be calculating in tableless model.
# in your controller
def calculating
Calculator.get_integral_log_and_furie params[:data]
end
# in your model
class Calculator
def self.get_integral_log_and_furie(data)
... # multi line code
end
end
So you can see that you can calculate it right in your controller, but it should be calculated in your model, so it is reusable and clean solution.
Another example is using some virtual attributes. Names. You can store first, second and third name in saparate columns, so you need to join it. You can create privae method in controler, but of course it is bad idea.
class User < AR::Base
def full_name
[first_name, second_name, third_name].compact.join(" ")
end
end
So you can call it everywhere in your project:
#user.full_name
# Peter Jhonson, or mu is too short
And so on and so on
Do model logic in models.
Maintain associations.
Maintain complex attributes.
Maintain validations.
Represent concepts from the business/industry.
Do controller logic in controllers.
Check that a user is authorized to modify a resource.
Pull and aggregate data to pass into a view template.
Find the right view template.
Compose json for the API response.
Retry failed saves.
Models do not need to be ActiveRecords. You can do a whole lot with models - the "core" of your appliation - that has nothing to do with persistence. Just don't put controller logic into these models.
That's a good question.
Even if you don't need to use a database, you can still take an OOP / MVC approach to organise your code and wrap your data, logic and behaviour in models.
Code organisation and encapsulation within model objects is still useful & important!
In Rails 3, you can make non-persisting models by including just some of the ActiveModel modules that ActiveRecord contains. For example:
# This class exists as a fairly simple DTO that is not expected to be persisted, but
# it does have validation, attributes & a hash constructor, just like ActiveRecord models
class MyProduct
include ActiveModel::Conversion
include ActiveModel::Naming
include ActiveModel::Validations
attr_accessor :title, :quantity
validates :title, :quantity, :presence => true
validates :quantity, :numericality => {:greater_than_or_equal_to => 1}
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end