Rails 3.1 Validations defined in database - ruby-on-rails

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

Related

How to use `first_or_initialize` step with `accepts_nested_attributes_for` - Mongoid

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

Converting Rails attributes to property-value pairs

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.

How to fail validation when there is no corresponding record (many to many relationship)

I have two models Link and Feed with many-many relationship (has many :through LinkFeed join model).
Each Link should belong to 1 or more Feeds. So I want to allow creating Link with (because there is corresponding Feed record):
#feed = Feed.create(name: "Test Feed")
#feed.links.create!(url: "http://google.com")
And fail to create link with simple Link.create!(url: "http://google.com") because it doesn't have corresponding Feed record. How can I do this?
Edit:
I added this validation:
validate do
errors.add(:base, "Must have at least one feed") unless feeds.size > 0
end
But now both examples fail with this error :/
You'll need a combination of validates_presence_of :feed_id, and validates_associated on your Link class. validates_associated ensures that the Feed is a valid object as well.
Try adding a must not be null condition to your foreign key feed_id.
class Link
validates :feed_id, :presence => true
...
end
This would keep records created without a association with feed to fail.
EDIT:
Here, two answers about validation in many-to-many relashionships.
Validate uniqueness of many to many association in Rails
Correct implementation of many-to-many in Ruby on Rails?

How does attr_accessible work in Rails?

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]

Signaling validation errors in assigning a virtual attribute?

This is a Rails/ActiveRecord question.
I have a model which basically has to represent events or performances. Each event has many attributions: an attribution is basically something like "In this event, Person X had Role Y".
I concluded that the best way to allow a user to edit this data is by providing a free text field which expects a structured format, which I'll call a role string:
singer: Elvis Costello, songwriter: Paul McCartney, ...
where I use autocompletion to complete on both the names of roles (singer, songwriter...) and the names of people. Both roles and people are stored in the database.
To implement this, I created a virtual attribute in the Event model:
def role_string
# assemble a role string from the associations in the model
end
def role_string=(s)
# parse a string in the above role string format,
# look up the People and Events mentioned, and update
# what's in the database
end
This is all fine. The whole thing works quite well, when the role string is well-formed and the associations given by the role string all check out.
But what if the role string is malformed? Okay, I figure, I can just use a regex together with standard validation to check the format:
validates_format_of :role_string, :with => /(\w+:\s*\w+)(,\s*\w+:\s*\w+)*/
But what if the associations implied by the role string are invalid? For example, what happens if I give the above role string, and Elvis Costello doesn't reference a valid person?
I thought, well, I could use validates_each on the attribute :role_string to look up the associations and throw an error if one of the names given doesn't match anything, for example.
My questions are two: first, I don't like this approach, since to validate the associations I would have to parse the string and look them up, which duplicates what I'd be doing in role_string= itself, except for actually saving the associations to the database.
Second, ... how would I indicate that an error's occurred in assigning to this virtual attribute?
First of all, you're attributing the Person to the Event incorrectly. You should instead pass a Person's ID to the event, rather than a string of the person's name. For instance, what if a Person with an ID of 230404 and a name of "Elvis Costello" changes his name to "Britney Spears?" Well, if that were to happen, the ID would remain the same, but the name would change. However, you would not be able to reference that person any longer.
You should set up your associations so that the foreign key references people in multiple cases:
has_one :singer, :class_name => "Person", :foreign_key => "singer_id"
has_one :songwriter, :class_name => "Person", :foreign_key => "songwriter_id"
This way, you can have multiple people associated with an Event, under different roles, and you can reference multiple attributes that Person may have. For example:
Event.first.singer.name # => "Elvis Costello"
Event.first.songwriter.name # => "Britney Spears"
You can research the available validations for associations (validates_associated), as well as whether or not an ID is present in a form (validates_presence_of). I would recommend creating your own custom validation to ensure that a Person is valid before_save. Something like (untested):
def before_save
unless Person.exists?(self.songwriter_id)
self.errors.add_to_base("Invalid songwriter. Please try again!")
return false
end
end
Also, I noticed that you're looking for a way for users to select a user which should be used for the roles in your Event. Here is what you can do in your form partial:
select(:event, :singer_id, Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => 'None' })
Hope this helps!

Resources