Rails modeling admin-controlled fields - ruby-on-rails

I have 10 drop-down fields in a "post" form that I want to be controlled by the admin user.
Currently, I have another model called "post fields"
class PostField
include Mongoid::Document
field :family, type: String
field :project, type: String
field :event, type: String
field :testmode, type: String
field :location, type: String
end
I then use these documents to populate each dropdown for a new post.
I have 2 dilemmas:
If the admin adds a PostField document for one dropdown, it creates a whole document. Not the most efficient thing, but I can deal with that.
If the admin changes the name of a field, all associated posts won't match.
This is effectively a tagging model, but poorly implemented b/c I'm not sure how to do it.
ie, I want to have a controlled list of locations: ["Upstairs", "Downstairs", "Bathroom"] so users can't just use a text input and say "Restroom" instead. But if an admin wants to change "Bathroom" to "Restroom", I want the posts to update accordingly.
Hope this isn't too complicated.

So you want each PostField to be of a certain type, or location? So you want to limit the values that go into :location?
What you have to do is create another model and table called locations, and that will contain a list of locations like Upstairs, Downstairs, Bathroom, etc. It has and id, and a name. In your PostField, you will join to the Locations with has_one :location, and the database table will have location_id. Then the admin can change Location.name to anything they want, while all the PostFields will show the updated label. In the view, use postField.location.name to display the label for that field.

Not sure to anderstand what you want to do.
With this schema, you should be able to create a "post" with static fields and dynamic fields.
PostField
The "sorting" field is used to sort post's field with respect to each other.
The "code" field will contain the code to identify post's field. Once created, it should be static.
If you want to make it dynamic, you should add a field "label" so administrator can add new post's field
PostFieldValue
The "sorting" field is used to sort values with respect to each other.
The "label" field is in your example ["Upstairs", "Downstairs", "Bathroom"]
In model
You should create a method to get the value based on post's field.
Improvements
You can improve the schema by adding a "isdefault" field to values so administrator can force the default value in drop-down.
You can categorize post's field if needed

Related

Django InLineSetModel and many2many field choices filtering

I have a many2many field in my model and I'm trying to render it in django admin using the inlineform. Everything is fine, but I can't customize the form as I want.
The model is simple there is a Supplier and a manytomany field named locations
Supplier(models.Model):
location = models.ManyToManyfield(Location, blank=True)
the inlineform
class LocationInLineForm(admin.TabularInLine):
model = Supplier.Location.through
The first thing I want to is to limit the choices. The constrains are two:
The location model has a boolean field 'active' and I need to filter based on that
The Supplier must be the Supplier selected in the django admin.
The admin user load the django admin, click on the supplier and the InLineForm must show his active location.
I thought that the following method is the right one to ovverride:
def formfield_for_manytomany(self, db_field, request, **kwargs):
but is not called by the InLineForm. The on called is:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
The example in the django docs are based on the request.user but what I need is the current supplier where I can get it? In the request? But where? request.GET is empty. Maybe I can get it from db_field, but I don't know how.
If I can get the supplier id, I can create a queryset and pass it using kwargs['queryset']. Am I right?

Changing the ID of a new object in Rails

So I have a Customer model in my Rails app and whenever a new customer is created I am getting a number as input from the user and I want that number to become the id of the customer on my database instead of the default 1, 2, 3, and so on. Is it possible to do that? If so, how?
EDIT: Let me be a more clear about what I want: I have a Customer and A Brand model. A customer has_many brands and a brand belongs_to a customer. Whenever the user creates a new brand, I want to connect it to its customer using that number that the user entered for the customer. Right now, whenever the user creates a new brand, I ask him to enter the customer_id value to connect the brand to a customer, but that is the id number generated by Rails. How do I make that customer_id attribute refer to the specific number the user entered for a customer, instead of the customer id generated by Rails.
It's possible, but don't do that. Just allow Rails to manage the auto-generated ID itself, and add this number - whatever it is - as a separate attribute in your model and saved to a separate field in your database.
As smathy mentioned, you can do the following:
1.First, generate a migration that add your custom attribute
rails g migration AddCustomerIdToCustomers customer_id:integer
2.When user enter the id, you would want to do something like this:
#customer.customer_id=params[:customer_id]
#customer.save
3.Then you can retrieve the custom object as
#customer = Customer.find_by_customer_id(customer_id)
where customer_id is the id you want.
As the above answer suggests, it is better let Rails handle the id attribute, because it is used extensively in Rails to handle database relationships ActiveRecord queries, etc. You do not want the extra headache of modifying it yourself.

ModelState.IsValid is false because CreatedBy field is null

I have an entity with, amongst others, the fields: CreatedBy, CreatedAt, ChangedAt
Now in the view, I do not want the user to fill these fields.
My approach was to fill them on the HTTP POST Action, before ModelState.IsValid check is made and before saving the data to the database.
However, ModelState.IsValid keeps returning false no matter what. What is the right way to implement this? Should I take the validation (ModelState.IsValid) from the POST action?
One problem, many solutions (from best to worst, in my opinion).
First solution
The best way would be to use a ViewModel (a class containing only the fields which must be edited by user and / or validated).
In this case, the CreatedBy, CreatedAt, ChangedAt fields would not appear in your ViewModel class.
Second solution
If you don't want ViewModel, you can put the "not mutable by user" fields as hidden fields in your view
#Html.HiddenFor(m => m.CreatedAt)
of course, hidden fields can be changed by user, so it's always an open door to unwanted datas...
Third solution
Clean ModelState from undesired errors before checking if it's valid (should become really boring if you've many fields concerned).
if (ModelState.ContainsKey("CreatedAt")) {
ModelState["CreatedAt"].Errors.Clear();
}
//then test for ModelState.IsValid()
Other solutions
WhiteList, BlackList for model binding, or... anything I forgot !
Many projects like to separate the View Model from the Domain Model. This allows you to create a View-Model class specifically tailored for the data you want to render and/or receive in a certain action while keeping the domain model correct/consistent.
In your View-Model class you would either not define any such property as the created date (since it is not supposed to be posted but is determined in the action). Or if you use one and the same View-Model for rendering and posting, and you want to render the date, you can make the date nullable (see Alexey's answer) on the View-Model while keeping it mandatory on the domain model.
DateTime is not nullable. If you want to keep those fields as null during model binding, if there is no values for them use:
Nullable<DateTime>
or shortcut:
DateTime?

Is there a way I can simply use a data annotation attribute to add a JavaScript attribute to a form field?

I would like to add a data-other-for attribute to a text input, to link it to a select, so that it can be used to capture a value not present in the select when the user selects 'Other' in the select. The attribute's code will determine which value or description is in fact 'Other', and if so, enable the text input and maybe make it mandatory.
It seems like the only way to do this is by creating a new helper, because going via a ValidationAttribute I can only add preset validation HTML attributes to my text input. Or go large and write a whole new metadata provider.
You could try to implement a custom ModelBinder.
Say, in the select you would have:
new SelectListItem(Text = "Other", Value="bind:propertyName", Selected = False);
Then in the overriden BindModel, you simply look for bind: in the model properties and when found, copy your value from there.
After this, you should be able to add normal validation attributes to your select list.

Insert link in String field if substring matches

I have a "comments" String field in my domain. On each save or update of the field I want to check if the field contains a sub string that matches a String from another field of the same domain (or different domain for that matter). I need to run through all the instances of that field to see if there is a match. If it matches I want to transform it into a link inside the comments field to a show action for that entry matching the sub string.
So for example, a comments field for a product with a serial number would note if the product has been replaced by another one by giving it's serial number in the comments field. Like: "This product was replaced by SN1234". I want to automatically transform SN1234 into a link to show the product with serial number SN1234.
What is the best way to go about this ? In the controller, in the GSP ? How ?
As long as the column you're trying to match on is indexed, you'll just need to do a query for the match and if found, modify your comment to include the URL. Controller or Service doesn't really matter for the lookup (although I would probably put it in a service). You'll want to be sure the search is not transactional so it will be as fast as it can be. No way I would do any of this in a GSP.
To insert the link, you could a simple find and replace. Once you know which text you want turned into the link, pseudo code follows:
def comment = "This product was replaced by SN1234"
def match = "SN1234"
def link = g.link(action: "show", controller: "product", id: "${product.id}", match)
comment = comment.replace(match, link)
Which you would then end up with
"This product was replaced by <a href='/product/1234'>SN1234</a>"
There may be more efficient ways to do this, but this is a good place to start.
You can use GORM events to do it in your domain. So whenever the domain is inserted/updated you can check that your field has been changed. Then you can insert your link.
def beforeInsert() {
yourMethod()
}
def beforeUpdate() {
if (isDirty('yourField')) {
yourMethod()
}
}

Resources