Changing ID to something more friendly - ruby

When creating a record the URL generated to view that record ends with its id
/record/21
I would like to be able to change that to something easier to read, such as my name and reference attributes from the model. I have looked at friendly_id but has trouble implementing a custom method to generate the URL
class Animal < ActiveRecord::Base
extend FriendlyId
friendly_id :name_and_ref
def name_and_ref
"#{name}-#{reference}"
end
end
I ended up getting an error
PG::UndefinedColumn: ERROR: column animals.name_and_ref does not exist LINE 1: SELECT "animals".* FROM "animals" WHERE "animals"."name_an... ^ : SELECT "animals".* FROM "animals" WHERE "animals"."name_and_ref" = 'Clawd-A123456' ORDER BY "animals"."id" ASC LIMIT 1
def show
#animal = Animal.friendly.find(params[:id])
end
I then come across the to_param method which Rails has available, in my model I have
def to_param
"#{self.id}-#{self.name}"
end
which will generate a URL for me of
/19-clawd
This works, but when I do the following it throws an error
def to_param
"#{self.name}-#{self.reference}"
end
My question though is how can I generate the URL to be name and reference without it throwing
Couldn't find Animal with 'id'=Clawd-A123456

If you would like to use your own "friendly id" then you'll need to adjust the find statement in your controller to something like
id = params[:id].split(/-/, 2).first
#animal = Animal.find(id)
Similarly, for the name/reference combination
name, reference = params[:id].split(/-/, 2)
#animal = Animal.find_by(name: name, reference: reference)
The second choice is a little more difficult because you'll have to do some work in the model to guarantee that the name/reference pair is unique.

The easiest way, is to go with friendly_id and simply add the missing database column. Keep in mind that you will need to ensure this new column is unique for every record. It basically acts as primary key.

Related

Refresh url if url parameter changes in rails

I have a url in this format.
/companies/:name?id=company_id
So for example the ABC company, assuming the id is 1,has as url
/companies/ABC?id=1
If someone changes the id parameter value, the company with the new id is correcty loaded and its view is shown but the url keeps showing the previous company name.
For example, having a second company DEF with id 2
/companies/ABC?id=2
Instead of
/companies/DEF?id=2
Is there a way to check that id change and reload the url with the correct company name?
Thank you guys.
The only way to ensure that the :name matches the :id is to compare them, and redirect if the value doesn't match what expected.
For example, you can add a before_action in your controller, or enhance the one where you load the company
before_action :find_company
def find_company
#company = Company.find(params[:id])
# check and redirect
if #company.name != params[:name]
redirect_to(company_path(...))
end
end
Of course, the comparison has to be adjusted depending on how you generate the value for the :name parameter.
Personally, I dislike the approach of having the id as query. I would probably prefer something like:
/companies/:id
and take advantage of the fact that any string with the format \d+-\w+ is translated into an integer in Ruby:
"12-company-name".to_i
=> 12
You can then have URLs like:
/companies/12-company-name
And simply use
Company.find(params[:id].to_i)
Of course, you will still need the before action if you want to redirect in case of name not matching the ID.
before_action :find_company
def find_company
id, name = params[:id].to_s.split("-", 2)
#company = Company.find(id)
# check and redirect
if #company.name != name
redirect_to(company_path(...))
end
end

ActiveRecord::AssociationRelation sql variables evaluation

I'm stuck in a very strange problem. Im my project I use 'load_and_authorize_resource' in order to retreive information from database.
Speaking more precisely, I have ItemsController and action #index. In this action, I have an instance variable #items of class ActiveRecord::AssociationRelation. If I pass any params to action index (for example, array of IDs), it will be merged automatically to this variable. But! #items.to_sql returns following:
SELECT "items".* FROM "items" WHERE "items"."user_id" = $1
So, some of my code just ain't wroking due to this strange variable in a query - "$1". What I need is to replace this variable with a real value that came from params. Of course, I've found a solution (hack):
#items.where_values.each{ |node| node.right = params[:user_id] if node.left.name = 'user_id' }
But actually I don't want to do such a hack. Maybe you folks know how to replace $1 with a params[:user_id]? Thanks you in advance.
Controller code:
class ItemsController < ApplicationController
load_and_authorize_resource :items
def index
ArchiveWorker.archivate(#items)
end
end

Duplicate an object while changing one of its attributes

I have a CartItem object that I want to make a duplicate of. Each CartItem belongs to a Cart.
I am writing a method that will take an old order and duplicate all of its cart_items and place it in the current cart.
order.add_items_to_cart(current_cart, current_user)
Order.rb
def add_items_to_cart(cart, cart_user)
cart.update_attributes(merchant_id: self.merchant_id)
self.cart_items.each do |ci|
new_cart_item = ci.dup
new_cart_item.save
new_cart_item.update_attributes(cart_id: cart.id, cart_user_id: cart_user.id)
end
end
Currently, I have the above. Is there a better way to dupe and change the attributes in one line?
If copying only attributes but associations is ok for you, then you are doing good. http://apidock.com/rails/ActiveRecord/Core/dup
But, I suggest you use assign_attributes, so you'll make only one query.
def add_items_to_cart(cart, cart_user)
cart.update_attributes(merchant_id: self.merchant_id)
self.cart_items.each do |ci|
new_cart_item = ci.dup
new_cart_item.assign_attributes(cart_id: cart.id, cart_user_id: cart_user.id)
new_cart_item.save
end
end
EDIT:
Make a method Cart#duplicate, which returns what you need
class CartItem
...
# returns copy of an item
def duplicate
c = Cart.new cart_id: cart.id, cart_user_id: cart_user.id
# copy another attributes inside this method
c
end
end
# And use it
self.cart_items.each do |ci|
new_card_item = ci.duplicate
new_card_item.save
end

Simplify multiple calls to ActiveRecord #find down to single line/SQL statement

I'm having a little brain block when it comes to condensing the use of two #find methods in ActiveRecord down to a single statement and SQL query.
I have a Sinatra route where the slug of both a parent and child record are supplied (the parent has many children). Atm I'm first finding the parent with a #find_by_slug call and then the child by #find_by_slug again on the matched parents association.
This results in two SQL queries that in my mind should be able to be condensed down to one easily... Only I can't work out how that's achieved with ActiveRecord.
Model, route and AR log below. Using ActiveRecord 3.2
Edit
I realised I need to clarify the exact outcome to require (I write this very late in the day). I only require the Episode but atm I'm getting the Show first in-order to get to the Episode. I only require the Episode and figured their must be a way to get at that object without adding the extra line and getting the Show first.
Model
class Show < ActiveRecord::Base
has_many :episodes
end
class Episode < ActiveRecord::Base
belongs_to :show
end
Sinatra route
get "/:show_slug/:episode_slug" do
#show = Show.find_by_slug show_slug
#episode = #show.episodes.find_by_slug episode_slug
render_template :"show/show"
end
ActiveRecord logs
Show Load (1.0ms) SELECT `shows`.* FROM `shows` WHERE `shows`.`slug` = 'the-show-slug' LIMIT 1
Episode Load (1.0ms) SELECT `show_episodes`.* FROM `show_episodes` WHERE `show_episodes`.`show_id` = 1 AND `show_episodes`.`slug` = 'episode-21' LIMIT 1
If you only need the #episode, you can maybe do
#episode = Episode.joins(:shows).where('shows.slug = ?', show_slug).where('episodes.slug = ?', episode_slug).first
If you also need #show, you've got to have two queries.

Factory - field with sequence as fallback

Question: How can i use for one field both sequence and transient attribute?
Background: I have factory, which has a name. The name is sequence to keep it unique. However in few specs i need it set name chosen by me so i can predict it in expectation. It's not a Rails project.
In my head it looks like name {attribute_from_create_call||FactoryGirl.generate :name}. But i don't know how to get the attribute which i give to the create method
Factory:
FactoryGirl.define do
sequence :name do |n|
'Testing Bridge '+n.to_s
end
factory :historical_bridge do
name {FactoryGirl.generate :name}
end
end
Usage of factory: FactoryGirl.create :historical_bridge, name: 'Bridge from '+Time.now.to_s
You can use FactoryGirl to create a hash of attributes with the sequences and then merge in whatever changes to that hash you want:
new_name = 'Bridge from '+Time.now.to_s
attr = FactoryGirl.attributes_for(:historical_bridge).merge(:name => new_name)
And then you could do something perhaps like create an object with those custom attributes:
HistoricalBridge.create(attr)
I think you should not use predefined name instead of sequence but check something like:
let(:bridge) { create :historical_bridge }
it { HistoricalBridge.something.name.should == bridge.name }

Resources