Setting associations in Rails app - ruby-on-rails

Im a little rusty to Rails and I'm trying to find an appropriate way to set a series of has_and_belongs_to_many associations. I have created a method, which I intend to make private, which sets the associations.
def update
def set_associations
industriesParams = params[:business][:industry_ids].reject{ |c| c.empty? }
#business.update(:industry_ids => industriesParams)
end
#business = Business.find(params[:id])
#business.set_associations
end
In this attempt, I am getting "undefined method `set_associations'" which I don't quite get. But I am also seeking a cleaner way of setting the associations from scratch.
Thanks, in advance.

This should do the work :
def update
#business = Business.find(params[:id])
set_associations
end
private
def set_associations
industriesParams = params[:business][:industry_ids].reject{ |c| c.empty? }
#business.update(:industry_ids => industriesParams)
end
But to make things shorter, you could even write the following :
def update
#business = Business.find(params[:id])
industriesParams = params[:business][:industry_ids].reject{ |c| c.empty? }
#business.update(:industry_ids => industriesParams)
end

Related

Ruby on Rails - how to handle belongs_to multiple on create

I'm trying to create a Comment that belongs_to multiple Models but I'm not sure on what the best practice is to assign it to both.
class Comment < ApplicationRecord
validates :message, :presence => true
belongs_to :user
belongs_to :discussion
end
When creating a Comment within a Discussion, I run into the error "Discussion must exist".
I'm using Devise so I am using current_user and building a Comment to align it with them, but I'm not sure on exactly how to point it to both. This seems to work, but seems very hacky.
def create
#comment = current_user.comments.build(comment_params)
#comment.discussion_id = params[:discussion_id]
...
end
What's the best practice?
I would add the :discussion_id to the comment_params method:
def create
#comment = current_user.comments.build(comment_params)
...
end
private
def comment_params
params.require(:comment)
.permit(...)
.merge(discussion_id: params.require(:discussion_id))
end
Or you could use build with a block:
def create
#comment = current_user.comments.build(comment_params) { |comment|
comment.discussion_id = params[:discussion_id]
}
# ...
end
No matter how you go about it, you do need to either set discussion_id or get it to be present in comment_params.
Does this look less hacky to you?
def create
comment_params.merge(discussion_id: params[:discussion_id])
#comment = current_user.comments.build(comment_params)
...
end
You can pass additional params in the block
params = ActionController::Parameters.new({
comment: {
name: 'Francesco',
body: 'Text'
}
})
comment_params = params.require(:comment).permit(:name, :body)
def create
#comment = current_user.comments.build(comment_params) do |comment|
comment.discussion_id = params[:discussion_id]
end
end
There are several things you need to keep in mind when you create an entity which is associated to more than one entity.
With respect to your question regarding to best practices. I would like to emphasise the usage of an interactor here:
# xx_controller.rb
def create
success = CreateComment.call(params)
if success
....
else
....
end
end
# ../../create_comment.rb
# pseudocode
class CreateComment
def call(params)
validate your params # check if nothing is missing, the discussion exists and you have a user
build_and_save_your_comment
return_your_result_object # return a tuple or a result or whatever you need in order to handle errors and success cases...
end
end
With this approach you keep the readability within your controller and you can focus on what matters to the comment creation in a dedicated place.

Dynamic Strong params for require - Rails

I have an update method right now that will not work for all situations. It is hard coded in the strong params like this params.require(:integration_webhook).permit(:filters) that all fine right now but sometimes it may be integration_webhook and other times it needs to be integration_slack. Basically, is there a way that I don't need to hardcode the require in the strong params? I'll show my code for clarity.
Update Method:
def update
#integration = current_account.integrations.find(params[:id])
attrs = params.require(:integration_webhook).permit(:filters)
if #integration.update_attributes(attrs)
flash[:success] = "Filters added"
redirect_to account_integrations_path
else
render :filters
end
end
As you can see it's a standard update method. But I need the integration_webhook params to be dynamic. I'm wondering if there is a model method I could call to strip away the integration_webhook part?
Not totally sure how dynamic this needs to be, but assuming that we are either getting an integratino_webhook or a integration_slack.
def update
#integration = current_account.integrations.find(params[:id])
if #integration.update_attributes(update_params)
# ...
else
# ...
end
end
private
def update_params
params.require(:integration_webhook).permit(:filters) if params.has_key?(:integration_webhook)
params.require(:integration_slack).permit(:filters) if params.has_key?(:integration_slack)
end
Checkout Strong parameters require multiple if this didn't answer your question.
Edit
For more dynamic requiring:
def update_params
[:integration_webhook, :integration_slack].each do |model|
return params.require(model).permit(:filters) if params.has_key?(model)
end
end
Off the top of my head something like this should work. The naming convention isn't the best but it the structure will allow you to just add to the list if you need to.
def update
#integration = current_account.integrations.find(params[:id])
if #integration.update_attributes(webhook_and_slack_params)
flash[:success] = "Filters added"
redirect_to account_integrations_path
else
render :filters
end
end
def webhook_and_slack_params
[:integration_webhook, :integration_slack].each do |the_params|
if(params.has_key?(the_params))
params.require(the_params).permit(:filters)
end
end

How to remove API logic from view in Rails Way?

I would like to remove this logic:
Suitcase::Hotel.find(id: hotel.id).images.first.url
from view.
https://gist.github.com/2719479
I dont have model Hotel. I get this url via API using Suitcase gem.
Problem is because
hotel is from #hotels = Suitcase::Hotel.find(location: "%#{headed}%") and API recevie me images only if do Suitcase::Hotel.find(id: hotel.id)
If Suitcase::Hotel.find(id: hotel.id).images.first.url works then i would guess hotel.images.first.url will work too if hotel is an hotel instance.
Is adding:
#hotel = Suitcase::Hotel.find(id: hotel.id)
to #show action doesn't work?
EDIT:
In that case make an helper:
def hotel_image_url(hotel)
Suitcase::Hotel.find(id: hotel.id).images.first.url
end
But as I can see here you can simply write in controller:
#hotels_data = Suitcase::Hotel.find(ids: #hotels.map(&:id))
Or to be more elegant add to your model (or create decorator (it's better option)):
def photo
Suitcase::Hotel.find(id: self.id).images.first.url
end
I think this should work, not sure about the second option
class Search < ActiveRecord::Base
attr_accessible :headed, :children, :localization, :arriving_date, :leaving_date, :rooms, :adults
def hotels
#hotels ||= find_hotels
end
private
def find_hotels
return unless headed.present?
#hotels = Suitcase::Hotel.find(location: "%#{headed}%")
#hotels.each do |hotel|
def hotel.image_url
Suitcase::Hotel.find(id: hotel.id).images.first.url
end
end
end
end
# or this, but I'm not sure if this works
#hotels.each do |hotel|
image_url = Suitcase::Hotel.find(id: hotel.id).images.first.url
def hotel.image_url
image_url
end
end

Rail dirty and date changes

Have a column in orders called closed_date which is a DateTime field.
Using Dirty. trying to do if self.closed_date_changed? but it's not working. Do I have to do something special for tracking changes with Date Time fields?
EDIT
Using Rails 3.0.3, Ruby 1.9.2p136
Code in orders controller
def update
#order = Order.find(params[:id])
if #order.update_attributes(params[:order])
#order.close_order
end
end
end
In Model
include ActiveModel::Dirty
def close_order
if self.closed?
if self.closed_date_changed?
self.items.each do |item|
item.update_attribute(:created_at, self.closed_date)
end
end
else
self.update_attributes(:closed_date => Time.now, :closed => true)
self.items.each do |item|
item.update_attribute(:created_at => Time.now)
item.schedule_any_tasks
end
end
end
end
I think you mean something like:
def save_changes
if closed_date_changed?
# do something like save the modified data to a table
else
# do anything else
end
end
And the most important, don't forget to call this method on a before_save(update) callback.
Because the changes only remains while the actual record isn't saved.
Hope it helps!
def start_date_changed?
return true if self.start_date != self.start_date_was
return false
end
I have use was which checks for the value change..
Thanx

Ruby on Rails: Equating items in controllers (or maybe models?)

I'm trying to make attributes equal predetermined values, and I'm not sure if I'm doing that efficiently with the following (in my orders controller):
def create
#order = Order.find(params[:id])
#order.price = 5.99
#order.representative = Product.find(params[:product_id]).representative
#order.shipping_location = SHIPPING_LOCATION
#order.user = current_user
respond_to do |format|
...
end
end
Is there a more efficient way to equate attributes in Rails (maybe using models)? If I'm using two different controllers, do I just repeat what I did above for the new controller?
Use before_create callback in model to assign default values.
Your code is a little off, it looks like a controller action for create, but the code reads like it's for an update.
Regardless...
You could use a parameter hash to update everything at once.
In the case where you're creating:
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
#order = Order.new(order_update)
In the case where you're updating:
#order.update_attributes(order_update) #attempts to save.
Mixing it into your controller code we get:
def create
#order = Order.find(params[:id])
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
respond_to do |format|
if #order.update_attributes(order_update)
# save succeeded. Redirect.
else
# save failed. Render with errors.
end
end
end
Another solution:
class Example < ActiveRecord::Base
DEFAULTS = HashWithIndifferentAccess.new(:some => 'default', :values => 'here')
def initialize(params = {})
super(DEFAULTS.merge(params))
end
end
Either use initialize and merge with params, or use an ActiveRecord hook like before_create etc.

Resources