I have a Media model that has a bunch of standard metadata attributes and is persisted in the database as normal. What I want to do now is to add some configurable metadata attributes to this model on top of the existing attributes. A list of these attributes will be defined in a config file and loaded in at runtime. They'll be stored in the database in a different table as a series of property-value pairs with an association to the main model.
So, my code currently is,
class Media < ActiveRecord::Base
has_many :custom_metadata
attr_accessible :title, :language, :copyright, :description
end
and
class CustomMetadata < ActiveRecord::Base
belongs_to :media
attr_accessible :name, :value
end
What I want to do is to be able to access and update the custom metadata attributes on the Media model in the same way as the standard metadata attributes. For example, if the custom metadata attributes are called publisher and contributor, then I want to access them in the Media model as #media.publisher and #media.contributor even though they will be in the association #media.custom_metadata where its values would be something like [{:name => 'publisher', :value => 'Fred'}, {:name => 'contributor', :value => 'Bill'}]
It seems to be that virtual attributes would be the best way of achieving this but all of the examples I can find of people using virtual attributes is where the names of the attributes are static and known rather than dynamic from a run-time configuration, so they can define methods such as publisher and publisher= which would then contain code to write to the relevant associated property-value record.
I can define attributes on the class with attr_accessor *Settings.custom_metadata_fields (assuming Settings.custom_metadata_fields returns [:publisher, :contributor]) and also allow mass-assignment using a similar technique with attr_accessible.
The part I get stuck on is how to populate the virtual attributes from the association when loading the data from the record and then, in reverse, how to pass the data in the virtual attributes back into the association before the record is saved.
The two ways I currently see this working are either using method_missing or attribute_missing, or perhaps via initialize and a before_save callback? In either case, I'm not sure how I would define it given that my model has a mix of normal attributes and virtual attributes.
Any suggestions?
Using callbacks sounds reasonable.
What database are you using? If PostgreSQL, maybe you should take a look at HStore extension (http://www.postgresql.org/docs/9.2/static/hstore.html)
it will perform better, and there are some gems making it easy to use.
After looking into the callbacks some more I discovered the after_initialize callback and this is much better than using the initialize method as I'd first planned.
In the end, this was the final code for the Media model and I didn't change anything in the CustomMetadata model from what I defined in the question,
class Media < ActiveRecord::Base
has_many :custom_metadata
attr_accessor *Settings.custom_metadata_fields
attr_accessible :title, :language, :copyright, :description
attr_accessible *Settings.custom_metadata_fields
validates_presence_of *Settings.required_custom_fields
before_save :save_custom_metadata
after_initialize :load_custom_metadata
def load_custom_metadata
MediaMetadata.custom_all_fields.each do |field|
custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
send("#{field}=", custom_record.value)
end
end
def save_custom_metadata
MediaMetadata.custom_all_fields.each do |field|
custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
custom_record.value = send(field)
if custom_record.value.blank?
custom_record.destroy
else
custom_record.save
end
end
end
end
This solution had a couple of nice benefits. Firstly, it doesn't affect any of the normal attributes on the Media model. Secondly, only custom metadata with actual values are stored in the custom metadata table. If the value is blank, the record is removed completely. Finally, I can use standard validations on the model attributes as shown for my required custom metadata attributes.
Related
hope someone can help me on this
I am using the rails serializers to format my model output to a RESTFul JSON web service.
Now my question is...
Say if I have a model user, that I will be using everywhere amongst the application. Some of the places I want pretty much all the user data (name, email, phone, addresses...etc.), while others I just want the name, email. However, these user objects are attachment to some main model via has_many, belongs_to...etc.
So how could I indicate which serializer I want for users at each specific output.
Thanks in advance
In your case, you want custom serializer each case, so use different serializers.
has_many :users, serializer: UserAllSerializer
Also this in case you want to remove the entire key, from the github page of rails serializer.
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body
has_many :comments
def filter(keys)
keys.delete :comments if object.comments_disabled?
keys
end
end
If you want to scope your serialization, take a look at this part
Here is my setup, followed by an explanation of what I am trying to accomplish.
class Layer < ActiveRecord::Base
has_and_belongs_to_many :components
end
class Component < ActiveRecord::Base
has_and_belongs_to_many :layers
end
class ImageComponent < Component
# I want this table to inherit from the Component table
# I should be able to add image-specific fields to this table
end
class VideoComponent < Component
# I want this table to inherit from the Component table
# I should be able to add video-specific fields to this table
end
What I want to be able to do:
layer.components << ImageComponent.create
layer.components << VideoComponent.create
In practice, I realize that ImageComponent and VideoComponent will actually have to inherit from ActiveRecord::Base. Is there any way to nicely implement model subclassing in Rails?
Right now I have my Component model setup to be polymorphic such that ImageComponent and VideoComponent each has_one :component, as: :componentable. This adds a layer of annoyance and ugliness to my code though:
image_component = ImageComponent.create
component = Component.create
component.componentable = image_component
layer.components << component
I guess a simple way to explain this is that I want to implement a habtm relationship between Layers and Components. I have multiple types of Components (i.e. ImageComponent, VideoComponent) that each have the same base structure but different fields associated with them. Any suggestions on ways this can be accomplished? I feel that I am missing something because my code feels hackish.
The "official" way to achieve this in Rails is to use Single Table Inheritance. Support for STI is built into ActiveRecord: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance
If you want to use Multi Table Inheritance you would have to implement it by yourself...
here the main issue is between the Component and its types and not Layer and Component. i had a similar problem. will explain the solution specific to ur problem.
Store the type(Image/Video) as resource for Component and have a controller for Component and not all the types()
let the model structure be as
Component < ActiveRecord::Base
accepts_nested_attributes_for :resource
belongs_to :resource, :polymorphic => true, :dependent => :destroy
def resource_attributes=(params = {})
self.resource = spec_type.constantize.new unless self.resource
self.resource.attributes = params.select{|k| self.resource.attribute_names.include?(k) || self.resource.class::ACCESSOR.include?(k.to_sym)}
end
#component will be either image or video and not both
Image < ActiveRecord::Base
has_one :component, as :resource
Video < ActiveRecord::Base
has_one :component, as :resource
and a single controller as ComponentsController for CRUD of Component. Since the Component accepts attributes for resource(ie image/video), u can save the component as well as the resource and add normal validations for each resource.
the basic view for adding a Component can be as
= form_for(#component, :url => components_path, :method => :post) do |f|
= fields of Component
= f.fields_for :resource, build_resource('image') do |image|
= fields of resource Image
= f.fields_for :resource, build_resource('video') do |video|
= fields of resource Video
the fields for Image/Video can be added using the helper method
module ComponentsHelper
def build_resource(klass)
klass = "{klass.capitalize}"
object = eval("#{klass}.new")
if #component.resource.class.name == klass
object = #component.resource
end
return object
end
end
since the Component can have only one related resource(image/video), u need to select the the resource type on the view(in my case it was a dropdown list) and depending upon the selected resource show its fields and hide/remove all other resources fields(if image is selected, remove video fields using javascript). When the form is submitted, the method from Component model filters out all the key-value pairs for the intended resource and creates the component and its related resource.
Also
1) keep the field names for each resource unique cause when the form is submitted, the hidden resource(unwanted resources) fields are submitted which overwrite the intended resource fields.
2) the above model structure gives problem for resource attr_accessor only(they are not accessible on rails console). it can be solved as
ACCESSOR = ['accessor1', 'accessor2'] #needed accessors
has_one :component, :as => :resource
attr_accessor *ACCESSOR
See how to implement jobpost functionality that has 3 fixed categoris
i hope this helps.
With STI, you are sharing the same table with several model classes, so if you want subclassed models to have unique fields (database columns), then they need to be represented in that common table. From the comments in your example, it appears that this is what you want.
There is a trick you can do, however, which involves having a string column in the table that each model can use to store custom serialized data. In order to do this, it has to be OK that these data elements aren't indexed, because you won't be able to easily search on them within SQL. Let's say you call this field aux. Put this in the parent model:
require 'ostruct'
serialize :aux, OpenStruct
Now let's say you want the fields called manager and experience in a subclassed model, but none of the other STI models need this field and you won't need to search based on these attributes. So you can do this in the subclassed model:
# gets the value
def manager
return self.aux.manager
end
# sets the value
def manager=(value)
self.aux.manager = value
end
# gets the value
def experience
return self.aux.experience
end
# sets the value
def experience=(value)
self.aux.experience = value
end
In this example, single table inheritance still works fine and you also get custom persistant attributes for subclassed models. This gives you the benefits of sharing code and database resources among several models, but also allows each model to have unique attributes.
I have a very simple model
class Lifestyle < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :profiles
end
that has a has_and_belongs_to_many relationship with Profile
class Profile < ActiveRecord::Base
attr_accessible ...
belongs_to :occupation
has_and_belongs_to_many :lifestyles
accepts_nested_attributes_for :lifestyles
end
I want to use ActiveAdmin to edit the Profile object, but also assign Lifestyles to a profile. It should be similar to dealing with belongs_to :occupation, as this is sorted out automatically by ActiveAdmin to a dropbox with the options pre-filled with available occupations.
I've tried to use the has_many form builder method, but that only got me to show a form to type in the name of the Lifestyle and on submission, it returned an error.
f.object.lifestyles.build
f.has_many :lifestyles do |l|
l.input :name
end
Error I get:
Can't mass-assign protected attributes: lifestyles_attributes
The perfect way for me would be to build several checkboxes, one for each Lifestyle in the DB. Selected means that the lifestyle is connected to the profile, and unselected means to delete the relation.
I'm having great doubts that this is possible using ActiveAdmin and without having to create very complex logic to deal with this. I would really appreciate it if you'd give your opinion and advise me if I should go this way or approach it differently.
After some research, I am ready to answer my own question.
First, I have to say thanks to #Lichtamberg for suggesting the fix. However, that only complicates things (also regarding security, though not an issue in this case), and doesn't help me reach my ideal solution.
Digging more, I found out that this is a very common scenario in Rails, and it's actually explained in Ryan Bates' screencast no #17.
Therefore, in Rails, if you have a has_and_belongs_to_many (short form HABTM) association, you can easily set the ids of the other associated object through this method:
profile.lifestyle_ids = [1,2]
And this obviously works for forms if you've set the attr_accessible for lifestyle_ids:
class Profile < ActiveRecord::Base
attr_accessible :lifestyle_ids
end
In ActiveAdmin, because it uses Formtastic, you can use this method to output the correct fields (in this case checkboxes):
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all
Also, I have simplified my form view so it's now merely this:
form do |f|
f.inputs # Include the default inputs
f.inputs "Lifestlyes" do # Make a panel that holds inputs for lifestyles
f.input :lifestyles, as: :check_boxes, collection: Lifestyle.all # Use formtastic to output my collection of checkboxes
end
f.actions # Include the default actions
end
Ok, now this rendered perfectly in the view, but if I try and submit my changes, it gives me this database error:
PG::Error: ERROR: null value in column "created_at" violates not-null constraint
: INSERT INTO "lifestyles_profiles" ("profile_id", "lifestyle_id") VALUES (2, 1) RETURNING "id"
I found out that this is due to the fact that Rails 3.2 doesn't automatically update the timestamps for a HABTM association table (because they are extra attributes, and Rails only handles the _id attributes.
There are 2 solutions to fix this:
Either convert the association into a hm:t (has_many, :through =>)
Or remove the timestamps from the table
I'm going to go for 2) because I will never need the timestamps or any extra attributes.
I hope this helps other people having the same problems.
Edit: #cdesrosiers was closest to the solution but I already wrote this answer before I read his. Anyway, this is great nevertheless. I'm learning a lot.
Active Admin creates a thin DSL (Domain-Specific Language) over formtastic, so it's best to look at the formastic doc when you need form customization. There, you'll find that you might be able to use f.input :lifestyles, :as => :check_boxes to modify a has_and_belongs_to_many relationship.
I say "might" because I haven't tried this helper myself for your particular case, but these things have a tendency to just work automagically, so try it out.
Also, you probably won't need accepts_nested_attributes_for :lifestyles unless you actually want to modify the attributes of lifestyles from profiles, which I don't think is particularly useful when using active admin (just modify lifestyles directly).
Add
attr_accessible :lifestyles_attributes
f.e.:
class AccountsController < ApplicationController
attr_accessible :first_name, :last_name
end
is there any way how to execute validation directives saved in database ?
See, i don't have staticaly defined attributes by database columns but are defined as row-type.
Examle:
Have groups and attributes. Every group should have different attributes.
Groups: id, name
Attributes: id, name
GroupAttributes: id, group_id, attribute_id, validation(serialize hash as string, same as rails validations)
Articles: id, group_id, attribute_id, value
Form is dynamicaly created based on which group is defined. Group 1 may have different validation for Attribute 1 than Group 1 for same Attribute.
But big problem are validations. I can't implicit define it in Model. Because i dont know which attributes are want to valide.
I have an idea evaluate string saved in database. But it is critical when there be injected code (which is very unespected).
Do you know any way how to resolve this ?
EDIT:
Principle is: I will write about CMS, it could be more clearer.
CMS have Articles/Posts and Post belongs to Group(Category).
Article should have many Attributes (saved in Attributes db table) like title, content, excerpt, thumbnail image, etc. But these attributes are not fixed in everyone article.
This is because Article belongs to Group. For example: news, posts, properties, reviews, etc.
And there is what i want. When i write some review, it is not neccessary put all fields(attributes) for all groups, only these which are needs.
So my idea is, that is easier to define attributes as "row type" instead "column type". Any time i'm able to add new, remove old for any Group. There could be tens of attributes (and different) for any group.
After neccessary joins all Models i want to define validation for each one attribute (column "validation" on top) and be used only for these attributes which are associated with group (GroupAttributes).
Give these attribute validations into the Model, like standard Rails "validates". Without complicated "if" and "unless" conditional statements.
Hope is more detailed than first one.
You can try make custom validator and then loop attributes for group and evaluate their validation parameters on the article. But you will need a proper hash for rails validations.
GroupAttributes.first.validation => {:presence => true,
:length => {:minimum => 3, :maximum => 254},
:uniqueness => true}
#app/models/article.rb
class Article << ActiveRecord::Base
vlidates_with ArticleValidator
end
#lib/article_validator.rb
class ArticleValidator << ActiveModel::Validator
def validate(record)
record.group.group_attributes.each do |group_attribute|
record.validates group_attribute.attribute.name, group_attribute.validation
end
end
end
I am not sure if does this works but hope it will be helpfull.
UPDATE: I was wrong it didn't work because validates is ActiveModel method not ActiveRecord like I thought. The right answer is you have to add before_validation callback and then setup the right validators.
class Article << ActiveRecord::Base
before_validation :set_validators
validates :group_id, :presence => true
privat
def set_validators
self.group.group_attributes.each do |group_attribute|
Article.validates group_attribute.attribute.name, group_attribute.validation
end
end
end
I just had a general question about Ruby on Rails and the attr_accessible attributes that go in the models (Rails 3). Can someone explain which model attributes are supposed to be defined there? I remember something about risk for mass assignment, though I'm not too knowledgeable in this aspect... Thanks :)
Imagine an order class with some fields:
Order.new({ :type => 'Corn', :quantity => 6 })
Now imagine that the order also has a discount code, say :price_off. You wouldn't want to tag :price_off as attr_accessible. This stops malicious code from being able to craft a post that ends up doing something like so:
Order.new({ :type => 'Corn', :quantity => 6, :price_off => 30 })
Even if your form doesn't have a field for :price_off, if it's just in your model by default it's available. A crafted POST could still set it.
Using attr_accessible white lists those things are can be mass assigned and protects fields that you want explicit control of in your code.
Difference between attr_accessor and attr_accessible has some additional links.
attr_accessible allows you to define a whitelist of attributes on the model that can be mass assigned. So if you have 10 attrs but only whitelist 3 of them, only those three can be mass assigned.
class Foo < ActiveRecord:Base
#lets say you have attrs one, two, three
attr_accessible :one, :two
end
#You can do this:
Foo.new({:one => 1, :two => 2})
#if you were to do this:
Foo.new({:one => 1, :two => 2, :three => 3})
#Foo's three attr would not be set
The Rails ActiveRecord documentation has some good detail on the topic.
Basically attr_accessible:
Specifies a white list of model attributes that can be set via
mass-assignment.
And attr_protected:
Mass-assignment to these attributes will simply be ignored, to assign
to them you can use direct writer methods. This is meant to protect
sensitive attributes from being overwritten by malicious users
tampering with URLs or forms.
Think of attr_accessible as a list of the attributes you want a user to be able to set through a form, anything not on this list wont be able to be set through the mass assignment which ensures that you keep the sensitive values in your database protected from a malicious user. This is a small step to keeping your application secure and you should take a look at the Rails Security Guide if you want to follow Rails best practices.
attr_accessible is the rails feature with the help of which we can permit mass-assignment for model attributes. It is just opposite to attr_protected in functionality.
To make a particular attribute available for mass-assignment we use attr_accessible as follows :
class Person < ActiveRecord::Base
attr_accessible : name
end
For more detailed explanation about attr_accessible and Strong parameters you can visit the link given below:
[http://findnerd.com/list/view/attr-accessible-in-Rails-4/3654/][1]