Rails to_param - Without ID - ruby-on-rails

I want to create param /users/will-smith, so here's my code:
def to_param
"#{full_name.parameterize}"
end
Parameterize will convert "Will Smith" to "will-smith"
So in the controller, the param won't match the find statement, thus return nil
# User with full_name "will-smith" not found
#user = User.find_by_full_name(params[:id])
Most of the solutions I found is by changing the param to #{id}-#{full_name.parameterize}. But I don't want the URL to contain the ID.
The other solutions is adding permalink column in the database, which I don't really like.
Any other solution?
Thanks

Here's a gem called FriendlyId. It will give you more options to play with.
The problem with your code is that you need to convert that parameter back to original, or use the same kind of transformation on your column during the search. FriendlyId, basically, helps you to achieve the same effect.
Also, I'm not sure, but you could miss that gist. It contaits lots of info on the topic.

Related

Reliable String interpolation in Rails

What is the best way to ensure that a model exists before doing string interpolation? I have a variable user, and I need to see what the user's major is. A table in between named user_attributes has the user's info.
#{user.user_attribute.major.name}
The user may not have specified a major yet, in which case they wouldn't have a major model instance yet. So when I try and get the name of the major, I would get an "undefined method on nil class type" error. Any advice on how to safely do this?
You could avoid try and add a method to your model or decorator..
def major_name
user_attribute.major && user_attribute.major.name
end
OR
def major_name
user_attribute.major.name if user_attribute.major?
end
Check: https://codereview.stackexchange.com/questions/28610/handling-nil-trying-to-avoid-try
You can use try method:
# if model is present
{user.user_attribute.major.try(:name)} # => "<MAJOR_NAME>"
# if model is NOT present
{user.user_attribute.major.try(:name)} # => ""
You can read more about try.
You could use the lonely operator. It is like try, but slightly less functional (which doesn't matter in your case).
user.user_attribute.major&.name
This might well be the 'worst' answer to ruby puritans, but it works for me in some scenarios:
"#{user.user_attribute.major.name rescue nil}"

Override ActiveRecord::Base find method (to accept non-default id field as a search parameter)

I have followed this tutorial on how to accept not-only-numeric primary key id when creating instance of my ModelName in my Ruby on Rails application.
Everything is okay, but there is a paragraph:
Be aware that Product.find won’t work anymore, and other Rails helper that relies on id will stop functioning. If you really want that, you need to override more methods and this seems too much of a pain for me. So I’d highly recommend you to leave #id as is.
The question is: when I am trying to get instance of my model by using .find() method in my ModelNameController it doesn't work (I think that's because of .find() method's search parameters - it does find something by id field which is numeric)
I have this piece of code:
def set_model_name
#model_name = ModelName.find(params[:hashid])
end
Where :hashid is a parameter that is a string (I'd like to use a string instead of a number)
How could I solve my problem?
One of the solutions would be overriding ActiveRecord::Base's .find() method.
Thanks in advance!
You do not need to override the default behaviour of find. Instead, you can use the find_by method:
def set_model_name
#model_name = ModelName.find_by(hashid: params[:hashid])
end

ruby on rails iterating params

I have a client that is sending params such as age, gender, name and so on.
I need to retrieve data from the table based on the params, but first I need to check for the presence of the param(to avoid a null param and therefore an empty result). The params are working as filters, so they can be triggered or they can be left blanck.
What I am doing right now is
#retieve = Student.all
unless params[:age].nil?
#retrieve = #retrieve.where(age: params[:age])
end
unless params[:gender].nil?
#retrieve = #retrieve.where(gender: params[:gender])
end
and so on for every param I receive. This way I check if the filter has been selected, and if it has I use the selection as a parameter for the query
It works, but as Ruby is known for the DRY statement, I am pretty sure someone out there knows a better way for putting this and to make this flexible.
Thank you for whatever answer or suggestion you will provide!
This will work best if all of these filters were in a subhash of params that you can iterate over without including unwanted parameters (eg the :action and :controller parameters that rails adds)
Once you've done that you could do
(params[:filters] || {}).inject(Student.all) do |scope, (key, value)|
scope.where(key => value)
end
There's a few ways to do this sort of thing and you have options for how far you want to go at this stage.
Two big things I'd consider -
1) Make nice scopes that allow you to send a param and ignore it if it's nil. That way you can just append another scope for each param from the form and it will be ignored without using if or unless
2) Move the search into a separate class (a concern) to keep your controller clean.
Here's a blog post that talks about some of the concepts (too much to post in this answer). There is lots of info on the web about this, I searched on the web under "rails search filter params concern" to get an example for you.
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/

Rename ActiveResource properties

I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.

Adding title to rails route

I have List objects which are shown like this:
www.mysite.com/lists/123
Where 123 is the id of the list. What I would like to do is add the title of the list the url so it it more informative(for google or whatever). So I would like it to look like:
www.mysite.com/lists/123/title-of-list-number-123
How do you go about adding to a url like this? If you just enter:
www.mysite.com/lists/123 w/o the title, should it find the title and then redirect to a new route?
If you want to keep your find-calls as they are (by id), you could do the opposite of what mplacona suggested:
def to_param
"#{id}-#{title.parameterize}"
end
With this, your find(params[:id]) will work because it'll convert the string to an integer (can only succeed if the number is in the beginning of the string). So this is will actually work:
List.find("123-my-title")
and will be the same as
List.find(123)
Read more about this and other ways to accomplish this here: http://gregmoreno.ca/how-to-create-google-friendly-urls-in-rails/
The parameterize will automatically convert the string to a "pretty" url. Read more here: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html#M001367
If you want a bit more functionality, I'll suggest friendly_id aswell.
This article says exactly what you need to do accomplish this.
http://railscasts.com/episodes/63-model-name-in-url
UPDATE
Have a permalink added to your model, and save as follow to it:
def to_param
"#{permalink}-#{id}"
end
On your controller, instead of getting things by the id, get them by the pemalink:
#product = Product.find_by_permalink(params[:id])
And that's all you need.
The screen cast explains all the steps on how to do it.
You could also take a look at friendly id, if you're in the mood for a gem/plugin.

Resources