I have a model StockEntry which belongs to a stock_logable for ex: InvoiceItem, ReturnItem etc. My model looks something like this:
belongs_to :stock_logable, polymorphic: true, optional: true
Now I need to handle the case of opening stock. This is how i handled it:
id: 1
stock_logable_type: "Opening Stock",
stock_logable_id: nil
quantity: 10
So basically I left the stock_logable_id blank and stock_logable_type a string. I don't have any model such as "OpeningStock" in my application.
Even though this works perfectly but i am still unsure whether this is a good way to handle the concerned scenario. Any insights/feedbacks will be helpful. Thanks
I don't know your business domain, but I'd rather create an OpeningStock model and set stock_logable to it. Setting polymorphic type without id may lead to nasty errors.
You can also do STI in StockEntries and keep there opening stock then. Or even without any inheritance - you can add state to the StockEntry to mark if it's opening or regular.
I am guessing you create the StockEntry first, and no "logable" (I personally prefer "loggable") is available/linked yet, and this is a special state called "opening stock", correct? You now seem to use the stock_logable_type to reflect this state. This is either because you also have StockEntry without a stock_logable for a different reason or to make your code/data more self-explanatory?
So if there are other states, I would create an actual state (use an enum or link to a domain table), but otherwise I would just leave the stock_logable unassigned completely, and maybe add a convenience method
def opening_stock?
stock_logable.blank?
end
to make your code more readable.
Related
I have a Question that has_many :answers (just like SO). I also want each question to have only 1 accepted_answer, so I just added an :accepted attribute to the Answer model that is simply a boolean.
So now, to get the accepted answer for my question, I have written a method on my model that just does this:
def accepted_answer
answers.where(accepted: true)
end
That allows me to do question.accepted_answer and it returns an ActiveRelation object like you would expect.
Nothing fancy. Simple and effective.
However, what I want to ensure though is that there can only be one answer on each question that is accepted: true at any moment in time.
What's the best way to approach this?
I thought about using a validator, but I couldn't find one that handled associated objects in this way. There are some that have bits & pieces that are interesting, but I can't quite fit all the pieces together. For instance, presence is interesting as is absence and validates_with (but this last one feels too heavy).
Suggestions?
Most likely the best way would be to use after_add callback (an example here), which would set to false all your existing accepted records via update_all and the latest answer with accepted set to true. It all depends on your logic.
You can also employ some other callbacks such as before_save, before_update and such with the similar functionality depending on your application specifics.
It is not quit a validation, but it will effectively maintain the required state of your model. Besides, the purpose of the validations to warn you when something is not valid, but I guess you want to save your object without such failures, and just enforce a single accepted answer.
Let me know in case you want to stop adding answers after the first one was accepted. In this case it would require a different functionality.
I have a little problem. I found it really hard to create a title for this, I hope I can explain it better:
One of our clients demanded that in every instance of "item_number", when it's from the Product model, the app will have to show it with 5 leading zeroes.
The "item_number" proprety in that model is in fact an integer on the database.
Our first approach would be just change in the model the value:
def item_number
item_number.to_s.rjust(5, '0')
end
Of course we get a:
SystemStackError at /
stack level too deep
And I'm pretty sure this will mess up when creating/updating records, but it doesn't matter, it doesn't work anyways.
The solution would be simple, just create:
def item_number_with_leading_zeroes
item_number.to_s.rjust(5, '0')
end
And replace item_number with it on our views.
But, we have over 5.000 usages of this, and some of them were not from this model (and we only need to replace the item_number on this model) and some of them are inside Iterators that we coded not using the parent object name (so I may not know if it's from this model or not), others are from custom form inputs, we can't just "replace all".
I would have to manually check every single instance of "item_number" and see if it's needed to change it to that new method or not. And I can still make mistakes and impact a lot of stuff.
I only need to change it on the views, I thought about a way to get it working with the helpers but that would still mean I would have to change it case-by-case.
There is another option to fix it, updating all records on the database to string with the zeroes and making a before_save option from now on, but we would like a rails solution first, before going that way (leaving the DB as is right now).
I'm out of ideas, I don't know if there is a solution, I thought about asking here first hoping someone can share some thought.
Is there any way I can do this without having to manually check the whole application?
Ruby 2, Rails 4.0
Thank you!
Try using read_attribute to avoid the recursion:
class Product < ActiveRecord::Base
...
def item_number
read_attribute(:item_number).to_s.rjust(5, '0')
end
...
end
I'm about to add a model to my app and I was inclined to call it "collection". It was also to have a field called "status" but it occurred to me that I think Rails uses both those terms elsewhere and this might be a problem worth avoiding.
Should I pick another model name instead of collection? and would state be better than status?
Thanks.
I've used both before - I think you should use whatever makes most sense to you, that aren't reserved words. If you think they will conflict with logic elsewhere in your app, then you might consider a different model name and/or column name.
I have an application where I would like to override the behavior of destroy for many of my models. The use case is that users may have a legitimate need to delete a particular record, but actually deleting the row from the database would destroy referential integrity that affects other related models. For example, a user of the system may want to delete a customer with whom they no longer do business, but transactions with that customer need to be maintained.
It seems I have at least two options:
Duplicate data into the necessarily models effectively denormalizing my data model so that deleted records won't affect related data.
Override the "destroy" behavior of ActiveRecord to do something like set a flag indicating the user "deleted" the record and use this flag to hide the record.
Am I missing a better way?
Option 1 seems like a horrible idea to me, though I'd love to hear arguments to the contrary.
Option 2 seems somewhat Rails-ish but I'm wondering the best way to handle it. Should I create my own parent class that inherits from ActiveRecord::Base, override the destroy method there, then inherit from that class in the models where I want this behavior? Should I also override finder behavior so records marked as deleted aren't returned by default?
If I did this, how would I handle dynamic finders? What about named scopes?
If you're not actually interested in seeing those records again, but only care that the children still exist when the parent is destroyed, the job is simple: add :dependent => :nullify to the has_many call to set references to the parent to NULL automatically upon destruction, and teach the view to deal with that reference being missing. However, this only works if you're okay with not ever seeing the row again, i.e. viewing those transactions shows "[NO LONGER EXISTS]" under company name.
If you do want to see that data again, it sounds like what you want has nothing to do with actually destroying records, which means that you will never need to refer to them again. Hiding seems to be the way to go.
Instead of overriding destroy, since you're not actually destroying the record, it seems significantly simpler to put your behavior in a hide method that triggers a flag, as you suggested.
From there, whenever you want to list these records and only include visible records, one simple solution is to include a visible scope that doesn't include hidden records, and not include it when you want to find that specific, hidden record again. Another path is to use default_scope to hide hidden records and use Model.with_exclusive_scope { find(id) } to pull up a hidden record, but I'd recommend against it, since it could be a serious gotcha for an incoming developer, and fundamentally changes what Model.all returns to not at all reflect what the method call suggests.
I understand the desire to make the controllers look like they're doing things the Rails way, but when you're not really doing things the Rails way, it's best to be explicit about it, especially when it's really not that much of a pain to do so.
I wrote a plugin for this exact purpose, called paranoia. I "borrowed" the idea from acts_as_paranoid and basically re-wrote AAP using much less code.
When you call destroy on a record, it doesn't actually delete it. Instead, it will set a deleted_at column in your database to the current time.
The README on the GitHub page should be helpful for installation & usage. If it isn't, then let me know and I'll see if I can fix that for you.
I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.