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.
Related
When creating app I had model Order with attribute delivery_option. After some time, I had to create "higher level" order to group orders and created MainOrder.
Now, Order delegates :delivery_option to MainOrder, but about 70% of MainOrders has delivery_option == nil because where created during migration, just to cover Orders from past. I couldn't fill up main_order.delivery_option = order.delivery_option because there can be many orders in one main_order, each with different :delivery_option.
Is it possible to somehow access order.delivery_option without hitting order.main_order.delivery_option?
code looks like this:
class MainOrder
has_many :orders
end
class Order
belongs_to :main_order
delegates :delivery_option, to: main_order, allow_nil: true
end
You can use the attributes method returning a hash with attribute values:
order = Order.first
# fetches delivery_option from the main_order
order.delivery_option
# returns value stored in orders table belonging to the order
order.attributes["delivery_option"]
# you can also use
order.attribute(:delivery_option)
I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!
I've got a legacy database schema which consists of objects similar to the following:
table=car
oid, something, something_else, ...
has many properties -> car_properties
table=car_properties
oid, car_id, keyname, value, ...
belongs to car
The Car object is actually (logically) a combination of the columns in the "car" table, and multiple rows in the "car_properties" table.
I'm looking at doing a parallel rewrite of the application which uses this schema, so I need some way to map this table schema back to a nice ActiveRecord object. Ideally I'd like each of the properties in the _properties table to be accessible as a method on the "Car" class, so I can change the underlying table later without breaking things.
I can statically define the methods in the Car class, but I want to ensure that the ActiveRecord magic works, so things like .save work, and I can change the underlying schema at a later date (realising this will probably be an outage to the application).
How would I go about doing this in ActiveRecord?
To Clarify:
Basically, I want the following to work
#car = Car.first
#car.something = something
#car.someprop = something
However in the above, #code.someprop is actually #car.properties.where( "keyname = ?", "someprop" ).value
Obviously I don't want to be doing a SQL Query every time for this though, so I'm looking for a nice way to do this.
Unless I'm underthinking it, it should be something as simple as:
class CarProperty < ActiveRecord::Base
set_primary_key :oid
belongs_to :car
end
class Car < ActiveRecord::Base
set_table_name :car
set_primary_key :oid
has_many :car_properties
accepts_nested_attributes_for :car_properties
end
I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.
models:
#StatusMessage model
class StatusMessage < ActiveRecord::Base
belongs_to :users
default_scope :order => "created_at DESC"
end
#User Model
class User < ActiveRecord::Base
has_many :status_messages
end
In controller I want to join these two tables and get fields from both table. for example I want email field from User and status field from StatusMessage. When I use :
#status = User.joins(:status_messages)
Or
#status = User.includes(:status_messages)
It gives me only the user table data.
How can I implement this requirement?
You need to use includes here. It preloads data so you won't have another SQL query when you do #user.status_messages.
And yes you can't really see the effect: you need to check your logs.
First of all, I don't think it is possible (and reasonable) what you want to do. The reason for that is that the relation between User and StatusMessage is 1:n, that means that each user could have 0 to n status messages. How should these multitudes of attributes be included in your user model?
I think that the method joints in class ActiceRecord has a different meaning, it is part of the query interface. See the question LEFT OUTER joins in Rails 3
There are similar questions on the net, here is what I have found that matches most:
Ruby on Rails: How to join two tables: Includes (translated for your example) in the user a primary_status_message, which is then materialized in the query for the user. But it is held in one attribute, and to access the attributes of the status_message, you have to do something like that: #user.primary_status_message.status
When you use #status = User.includes(:status_messages) then rails eagerley loads the data of all the tables.
My point is when you use this User.includes(:status_messages) it will loads the data of status_messages also but shows only users table data then if you want first user status_messages then you have to #user.first.status_messages