Changing a proprety inside a rails model just for the views - ruby-on-rails

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

Related

Ruby On Rails: Disable `delete_all` when table name is present

delete_all is useful, but I never want to see it called on the same line with a table name. I'd like to disable things like TableName.destroy_all in both console and code.
One interesting issue happened earlier this month:
Application.destroy_all was called on a model instead of applications.destroy_all
(the model has_many applications)
For somebody new to ROR, it looks very similar, but the results were disastrous.
I'm open to some form of lint/code style tool, but that really wouldn't catch it in the console scenario. (Plus, I haven't been able to get rubo-cop to do something like this yet)
Basically, I'm asking for a way to make the console and codebase more secure so that newer developers can't inadvertantly delete everything in a table.
I'm not entirely clear on what you are trying to accomplish, but you could try overriding the method in your ApplicationModel with something like this (assuming Rails 5 or greater, or otherwise a root model in existence).
class ApplicationModel < ActiveRecord::Base
def self.destroy_all(*args)
raise('Cannot destroy all records of a model this way. Did you mean to delete a subset of records instead?')
end
end
Possibly make this method private if you'd like it even harder to run...
def self.destroy_all(*args)
raise('Cannot destroy all records of a model this way. Did you mean to delete a subset of records instead?')
end
private_class_method :destroy_all
You could get fancy and allow this to be bypassed with a special argument that you check for, but give this a try and see how it goes.

Ruby on Rails: Creating a link based on new database entry

I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.

Rails - Force model to be created via factory method

I'm using Rails 4. I have a class, Cart, which needs to be accessed within my application.
I want it accessed using the factory pattern:
class CartFactory
def self.obtain_cart_for_user(user)
...
end
end
I need this approach because sometimes, I want to return an existing cart and sometimes create a new one (based upon the age of the cart, its contents, whether the products in it are still available etc).
This is easy enough.
However, I also want to make sure some other future programmer doesn't instantiate a cart directly, or fetch one by any other means, including via model associations, such as:
Cart.new(...)
user.carts.new(...)
Cart.find(id)
Cart.find_by_attribute(blah: blah)
Cart.where(...).first
Is there any way to prevent that?
Well, it's possible to make the constructor private:
private_class_method :new
And of course, you can try making the ActiveRecord query methods (.find, .where etc.) private as well. But to me that sounds like a good way to end up with erratic behaviour. If you were to go this route, make sure your app is thoroughly tested first.
Another route would be for Cart not to extend ActiveRecord::Base (which I'm assuming it does), and instead include only the parts you need, like ActiveRecord::Persistence. If you are willing to dive in deep, check out the parts that are included in the source for ActiveRecord::Base.
Edit: Still one option would be to make Cart itself private within a module that only exposes CartFactory. There's no built-in syntax for a "private class", but it's possible to achieve since Ruby classes are just regular objects. Again, no idea how well ActiveRecord would deal with that.
But lastly there is of course the question of whether you want to do this at all. In general, Ruby is not very good at protecting you from yourself. :) As expressed in the latter linked answer, documentation and trust go a long way.

rails associations & object identity: maintaining object identity through a series of changes

`First of all, the fact that I am even asking this question implies that I am consciously choosing not to (strictly) obey the law of Demeter.
Since sometime (probably rails 3?) referring to model.association.first results in a new object each time, unless you use .to_a on the association:
campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fdd02c7fd58>"
campaign.campaign_shirts.first.to_s
=> "#<CampaignShirt:0x007fdd02ca6318>"
c.campaign_shirts.to_a.first.to_s
=> "#<CampaignShirt:0x007fdd02d13170>"
c.campaign_shirts.to_a.first.to_s
=> "#<CampaignShirt:0x007fdd02d13170>"
I've worked on several Rails 3/4 applications without even noticing this, probably because I do try to respect Demeter as much as is practical.
In this case I want Campaign to be in control, because it is a big state machine where many of its state changes involve transactionally coordinating changes in itself and various child objects.
Is there a way to freeze the association arrays at create and/or fetch time?
EDIT: I noticed almost immediately that they are frozen when you use Campaign.includes(...).find, which I am doing in my app. However I still have a problem in specs where the objects are factories created by FactoryGirl. Is there a way to say "freeze all the associations on this object" or do I have to call .to_a on each of them?
EDIT 2: I still have a problem when I refer to campaign through a belongs_to on user. (this seemed like a separate question, so I asked it here).
EDIT 3: the problem with the belongs_to includes extension was just syntax, so I'm removing the details of that.
So, my remaining problem is to get User.selected_campaign to act like it does in my app when it is built up by FactoryGirl. I'm going to try just doing a .reload at the start of each spec, which should trigger the includes extensions, at the cost of some spec performance.
I don't know the (gory) details of your setup, but what if you just memoize first on the Campaign object?
def Campaign
def first_campaign_shirt
#first_campaign_shirt ||= campaign_shirts.first
end
end
I think this way you obey the Law of Demeter again? But it might get annoying if you need more getters than just first_shirt. So consider this just a suggestion that won't fit in a comment box. :)
All of my in-app use cases were solved by carefully choosing where to add includes(..) to associations and scopes.
I was only able to solve the factory girl problem by calling reload on a factory object after creating it.

Passing parameters as path variables in ruby on rails

I'm still new to ROR, so pardon the simplicity of the question...
So http://www.example.com/controller/:id displays a record in my table, with :id being a number (1,2,3 etc.).
Is there a way I can have :id in the URL be the value of a field in the displayed record? Such that I can have http://www.example.com/controller/record_field? I want to have a human-friendly reference to specific records in my table. I'm sure this must be possible. Do I change something in routes.rb?
Thanks for the help!
The cleanest way is to add a new find method in your model (or simply use the find_by_fieldname Rails gives you in your control). Then you'll have your controller use that method instead of the regular find(params[:id]) to pull your model record.
Check out Ryan B's screencast on this here. It's pretty easy, and he's a good teacher, so you shouldn't have any problems.
I use the excellent rails plugin named friendly_id.
http://github.com/norman/friendly_id/tree/master
That should sort you out nicely. It is well documented too.
Take care around fields that might have modern Greek characters—might need to figure a work around for those.
Jon Smock's solution will work, too. I tend to prefer the following.
class Hamburger << ActiveRecord::Base
#this normally defaults to id
def to_param
name
end
end
class SomeModelController << ApplicationController
def show
#hamburger = Hamburger.find(params[:id]) #still default code
end
end
#goes in some view
This is the <%= link_to "tastiest hamburger ever", url_for(#hamburger) %>.
This is, loosely speaking, an SEO technique (beautiful URLs are also user-friendly and I suggest them to absolutely everyone even if you don't care about SEO, for example on pages behind a login). I have a more extended discussion of Rails SEO, which includes other tips like this, here.
Important tip: You should consider, at design-time, what you are going to do if the param should change. For example, in my hamburger scenario, it is entirely possible that I might rename "Sinfully Delicious Cheeseburger" to "Triple Bypass". If that changes URLs, there are some possible implications, such as breakage of customer links to my website. Accordingly, for production use I usually give these models an immutable permalink attribute which I initialize to be human-meaningful exactly once. If the object later changes, oh well, the URL stays the same. (There are other solutions -- that is just the easiest one.)

Resources