How to remove API logic from view in Rails Way? - ruby-on-rails

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

Related

Setting associations in Rails app

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

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.

Save external Tweets in database in Rails

I am new to rails developement and to the MVC architecture. I have a little application where I can add Videos' URLs from Dailymotion or Youtube and get the tweets related to that URL using the twitter gem in Ruby on Rails.
Now i'm able to store the tweets like this : (This is the video controller)
def show
#video = Video.find(params[:id])
# Creating a URL variable
url = #video.url
# Search tweets for the given video/url
#search = get_client.search("#{#video.url} -rt")
# Save tweets in database
#search.collect do |t|
tweet = Tweet.create do |u|
u.from_user = t.user.screen_name.to_s
u.from_user_id_str = t.id.to_s
u.profile_image_url = t.user.profile_image_url.to_s
u.text = t.text.to_s
u.twitter_created_at = t.created_at.to_s
end
end
I'm not sure if this is the right way to do it (doing it in the controller ?), and what I want to do now is to specify that those tweets that have just been stored belong to the current video. Also I would like to have some sort of validation that makes the controller look in the database before doing this to only save the new tweets. Can someone help me with that ?
My models :
class Video < ActiveRecord::Base
attr_accessible :url
has_many :tweets
end
class Tweet < ActiveRecord::Base
belongs_to :video
end
My routes.rb
resources :videos do
resources :tweets
end
This is an example of a "fat controller", an antipattern in any MVC architecture (here's a good read on the topic).
Have you considered introducing a few new objects to encapsulate this behavior? For example, I might do something like this:
# app/models/twitter_search.rb
class TwitterSearch
def initialize(url)
#url = url
end
def results
get_client.search("#{#url} -rt")
end
end
# app/models/twitter_persistence.rb
class TwitterPersistence
def self.persist(results)
results.map do |result|
self.new(result).persist
end
end
def initialize(result)
#result = result
end
def persist
Tweet.find_or_create_by(remote_id: id) do |tweet|
tweet.from_user = screen_name
tweet.from_user_id_str = from_user_id
tweet.profile_image_url = profile_image_url
tweet.text = text
tweet.twitter_created_at = created_at
end
end
private
attr_reader :result
delegate :screen_name, :profile_image_url, to: :user
delegate :id, :user, :from_user_id, :text, :created_at, to: :result
end
Notice the use of find_or_create_by ... Twitter results should have a unique identifier that you can use to guarantee that you don't create duplicates. This means you'll need a remote_id or something on your tweets table, and of course I just guessed at the attribute name (id) that the service you're using will return.
Then, in your controller:
# app/controllers/videos_controller.rb
class VideosController < ApplicationController
def show
#tweets = TwitterPersistence.persist(search.results)
end
private
def search
#search ||= TwitterSearch.new(video.url)
end
def video
#video ||= Video.find(params[:id])
end
end
Also note that I've removed calls to to_s ... ActiveRecord should automatically convert attributes to the correct types before saving them to the database.
Hope this helps!

How to duplicate a record in Rails except for one attribute?

In my Rails application I have a method that duplicates an invoice including its items.
class Invoice < ActiveRecord::Base
def duplicate
dup.tap do |new_invoice|
new_invoice.date = Date.today
new_invoice.number = nil <<<--------------
items.each do |item|
new_invoice.items.push item.dup
end
end
end
end
Now what I would like is to not copy the number attribute at all, so a new number can be generated inside my new action (I am not showing that here for brevity).
Right now, I am setting it to nil which is not what I want.
Any Ideas ?
Something along these lines perhaps:
def duplicate
new_invoice = Invoice.new(attributes.except("id", "number"))
items.each do |item|
new_invoice.items.push item.dup
end
new_invoice
end
Or loop over the attributes if you need to get around protected attributes etc. But self.attributes with Hash#except is probably what you want.
Pass the new number as a parameter, it's the most simple and most readable technique
class Invoice < ActiveRecord::Base
def duplicate(new_number = nil)
dup.tap do |new_invoice|
new_invoice.date = Date.today
new_invoice.number = new_number
items.each do |item|
new_invoice.items.push item.dup
end
end
end
end
Then in your controller
class InvoicesController < ApplicationController
def new
...
#new_invoice = #invoice.duplicate(new_number)
end
end
Why not
class Invoice < ActiveRecord::Base
def clone
number = self.number
cloned = super
cloned.number = number
end
end

Rails 3: alias_method_chain to set specific attribute first

When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end

Resources