dup or clone action in rails - ruby-on-rails

Well im going to clarify im doing it
class DuplicatesController < ApplicationController
before_action :set_venue, only: [:new]
def new
end
def create
if #venue.duplicate(venue_params)
flash[:success] = t('controller.create.success',
resource: Venue.model_name.human)
redirect_to admin_venue_url #venue
else
flash[:warning] = #venue.errors.full_messages.to_sentence
render :new
end
end
private
def set_venue
#venue = Venue.friendly.find params[:venue_id]
end
end
def venue_params
params.require(:venue).permit(:name,
:address,
:address_complement,
:city,
:phone)
end
end
def duplicate
(name,
address,
address_complement,
city,
phone)
new_venue = self.dup
new_venue.update_attributes(description: self.description,
logo: self.logo,
opening_time: self.opening_time,
closing_time: self.closing_time,
ally_id: self.ally_id)
new_venue.save
end
How can I call those params in my duplicates controller, thanks
I need to set the attributes, after create a dup because I want to save a new record with new information, but i dont know to do it in my method, someone could explain me
Thanks.

Probably the best way to do it is to pass only id/slug of original model.
Then your duplicates_controller.rb can look similar to this:
class DuplicatesController < ApplicationController
def create
old_venue = Venue.friendly.find(params[:id])
venue = old_venue.dup
venue.attributes = venue_params
if venue.save
# success render
else
# error render
end
end
private
def venue_params
params.require(:venue).permit(:permitted_attributes) # id should not be there
end
end
Of course you can refactor it, but I do not think it is needed in this situation.
Or my favourite is to change VenueController#create to something like this to allow creating from another instance:
if copy_from_id = params[:copy_from_id]
#copy_source = Venue.find_by(id: copy_from_id)
#venue = #copy_source.dup
#venue.attributes = venue_params
else
#venue = Venue.new
end
if #resource.save
...
else
...
end

Related

Rails 5 manage result from monads

I've got Rails 5 app with dry-monads on board. Monads are used to create the Appointment object inside create action in AppointmentsController. They return Success or Failure in the last step with below structure:
# services/appointments/create.rb
(...)
def call
Success(appointment_params: appointment_params)
(...)
.bind(method(:save_appointment))
end
private
def save_appointment(appointment)
if appointment.save
Success(appointment)
else
Failure(failure_appointments: appointment, appointments_errors: appointment.errors.full_messages)
end
end
After each action (success or failure) I want to send an email and display the corresponding json in AppointmentsController:
class Api::AppointmentsController < ApplicationController
def create
succeeded_appointments = []
failure_appointments = []
appointments_errors = []
batch_create_appointments_params[:_json].each do |appointment_params|
appointment = ::Appointments::Create.new(appointment_params).call
if appointment.success?
succeeded_appointments << appointment.value!
else
failure_appointments << appointment.failure[:failure_appointments] &&
appointments_errors << appointment.failure[:appointments_errors]
end
end
if failure_appointments.any?
AppointmentMailer.failed_mail(email, failure_appointments.size, appointments_errors).deliver_now
render json: {
error: appointments_errors.join(', '),
}, status: :bad_request
elsif succeeded_appointments.any?
AppointmentMailer.success_mail(email, succeeded_appointments.size).deliver_now
render json: {
success: succeeded_appointments.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end,
}
end
end
I wonder if there is a better, faster way to record these errors than declaring 3 different empty arrays (succeeded_appointments, failure_appointments, appointments_errors) like at the beginning of create action? so far the create action looks heavy.
Create a separate service object for bulk creation:
# services/appointments/bulk_create.rb
class Appointments::BulkCreate
def initialize(bulk_params)
#bulk_params = bulk_params
end
def call
if failed_results.any?
AppointmentMailer.failed_mail(email, failed_results_errors.size, failed_results_errors).deliver_now
Failure(failed_results_errors.join(', '))
else
AppointmentMailer.success_mail(email, success_appointments.size).deliver_now
Success(success_appointments)
end
end
private
attr_reader :bulk_params
def failed_results
results.select(&:failure?)
end
def success_results
results.select(&:success?)
end
def success_appointments
#success_appointments ||= success_results.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end
end
def failed_results_errors
#failed_results_errors ||= failed_results.map do |failed_result|
failed_result.failure[:appointments_errors]
end
end
def results
#results ||= bulk_params.map do |appointment_params|
::Appointments::Create.new(appointment_params).call
end
end
end
Then your controller will look like this:
class Api::AppointmentsController < ApplicationController
def create
result = ::Appointments::BulkCreate.new(batch_create_appointments_params[:_json]).call
if result.success?
render json: { success: result.value! }, status: :ok
else
render json: { error: result.failure }, status: :bad_request
end
end
end

Issue with HomeController showing undefined method

I am trying to pass stored_products from shopify into a Rails app but keep getting a home controller error at https://f588240c.ngrok.io/ i have made updates, with no luck and restarted the server a number of times with no luck.
Any help would be welcomed. Heres the code
class Api::V1::HomeController < ShopifyApp::AuthenticatedController
def index
#products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
#products.each do |product|
StoredProduct.where(shopify_id: product.id)
.first_or_create do |stored_product|
stored_product.shopify_id = product.id
stored_product.shopify_title = product.title
stored_product.shopify_handle = product.handle
stored_product.shopify_image_url = product.image.src
stored_product.shop_id = #shop.id
stored_product.save
product.images.each do |image|
ProductImage.where(shopify_id: image.id)
.first_or_create do |product_image|
product_image.image_url = image.src
product_image.stored_product_id = stored_product_id
product_image.shopify_id = image.id
end
end
end
end
#stored_products = StoredProduct.belongs_to_shop(#shop.id)
end
end
From the authenticated controller
private
def set_shop
#shop = Shop.find_by(id: session[:shopify])
set_locale
end
from the store_products.rb file
class StoredProduct < ApplicationRecord
belongs_to :shop
has_many :product_images
scope :belongs_to_shop, -> shop_id { where(shop_id: shop_id) }
end
For this specific issue/code tutorial, the private set_shop method should be set like follows:
def set_shop
#shop = Shop.find_by(id: session[:shop_id])
set_locale
end
The other answer has params instead of session
The problem is that #shop is nil. The error message says it cannot call the method .id on NilClass.
In the image I can see that you have a shop_id in the params so you might just need to change your code here:
def set_shop
#shop = Shop.find_by(id: params[:shop_id])
set_locale
end
But that depends on your code, so please double check.

How to set karma to appropriate user

I'm trying to add a user karma feature to my app and I'm almost done, just that the karma is being awarded to a different user.
NB, My like system is from scratch and not acts_as_votable.
What I want:
When a user upvotes a book, I want a +1 karma be awarded to the
book.user
If a user's books are downvoted more then they upvoted, I want such
user have negative karma.
What I'm getting:
When a book is upvoted, the user who upvoted the book gets the +1
karma instead of the book.user.
When a user with 0 karma gets his/her book downvoted, the karma incrment by 1 instead of decrementing.
class AddKarmaToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :karma, :integer, default: 0
end
end
My code:
vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :book
validates_uniqueness_of :user_id, scope: :book_id
after_create :increment_vote, :add_karma
after_destroy :decrement_vote, :remove_karma
private
def increment_vote
field = self.upvote ? :upvotes : :downvotes
Book.find(self.book_id).increment(field).save
end
def decrement_vote
field = self.upvote ? :upvotes : :downvotes
Book.find(self.book_id).decrement(field).save
end
def add_karma
user = User.find(self.user_id)
user.increment(:karma, 1).save
end
def remove_karma
user = User.find(self.user_id)
user.decrement(:karma, 1).save
end
end
votes_controller.rb
class VotesController < ApplicationController
def create
book_id = params[:book_id]
vote = Vote.new
vote.book_id = params[:book_id]
vote.upvote = params[:upvote]
vote.user_id = current_user.id
#check if vote by this user exists
existing_vote = Vote.where(user_id: current_user.id, book_id: book_id)
#new_vote = existing_vote.size < 1
respond_to do |format|
format.js {
if existing_vote.size > 0
#destroy existing vote
existing_vote.first.destroy
else
#save new vote
if vote.save
#success = true
else
#success = false
end
# #total_upvotes = #book.upvotes
# #total_downvotes = #book.downvotes
end
#book = Book.find(book_id)
#is_upvote = params[:upvote]
render "votes/create"
}
end
end
private
def vote_params
params.require(:vote).permit(:upvote, :book_id)
end
end
First of all when using active record relations you don't need to call Model.find in the class, just call the relation with it's name:
def increment_vote
field = self.upvote ? :upvotes : :downvotes
book.increment(field).save
end
def add_karma
user.increment(:karma, 1).save
end
In add_karma and remove_karma you are referencing the user that the vote belongs to, and not the user that owns the book. To achieve your goal you should also increment and decrement karma on the book's owner:
def add_karma
user.increment(:karma, 1).save
book.user.increment(:karma, self.upvote ? 1 : -1).save
end
def remove_karma
user.increment(:karma, 1).save
book.user.decrement(:karma, 1).save
end
You could rewrite your controller to simplify the code:
class VotesController < ApplicationController
def create
#vote = current_user.votes.find_or_initialize_by vote_params[:book_id]
#vote.assign_attributes vote_params
#success = #vote.save
# instead of #book = #vote.book just use #vote.book in your view
#book = #vote.book
# instead of #is_upvote you can use #vote.upvote in your view
#is_upvote = #vote.upvote
respond_to do |format|
format.js { render 'votes/create'}
end
end
private
def vote_params
params.require(:vote).permit(:upvote, :book_id)
end
end

Manually assigning parent ID with has_many/belongs_to association in custom class

I'm using a custom class to make AR instances from Feedjirra. I can't get the children instances to relate to their parent objects.
Show has_many :episodes -
Episode belongs_to :show -
show_id is always nil.
RSpec logs #show.id and #episode.show_id as equal to one another. However when I run episode = Episode.first after running an import in development, the episode has its show_id set to nil.
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
...
if #show.save
puts "#show.id: #{#show.id}"
end
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
...
if #episode.save
puts "#episode.show_id: #{#episode.show_id}"
end
end
I tried using #episode = #show.episodes.create, as well as #episode = Episode.new with #episode.show_id = #show.id. They all log matching IDs but show_id is still nil on the instances. Every other column is filled in correctly.
I thought the issue may have had to do with using add_foreign_key:
class AddShowToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true
add_foreign_key :episodes, :shows, column: :show_id
end
end
So I removed that and used the standard foreign_key: true but it had no effect.
class RemoveShowFromEpisodes < ActiveRecord::Migration
def change
remove_column :episodes, :show_id
end
end
class AddShowBackToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true, foreign_key: true
end
end
Here's the full code in case it helps.
importers_controller.rb:
class Admin::ImportersController < Admin::ApplicationController
before_action :set_importer, only: [:show, :edit, :update, :destroy]
def index
#importers = policy_scope(Importer)
end
def show
end
def new
#importer = Importer.new
authorize #importer
end
def create
#importer = Importer.new(importer_params)
authorize #importer
if #importer.save
require "subscription_importer"
SubscriptionImporter.new(#importer)
flash[:notice] = "Importer added."
redirect_to admin_importers_path
else
flash[:error] = "Importer not added."
render "new"
end
end
def edit
end
def update
end
def destroy
end
private
def set_importer
#importer = Importer.find(params[:id])
authorize #importer
end
def importer_params
params.require(:importer).permit(:name, :url, :source)
end
end
subscription_importer.rb:
class SubscriptionImporter
def initialize(importer)
#importer = importer
#feed = Feedjira::Feed.fetch_and_parse #importer.url
if #importer.source === "iTunes"
itunes_parser(#importer)
end
end
def itunes_parser(importer)
#importer = importer
# Parser
#feed = Feedjira::Feed.fetch_and_parse #importer.url
# Show
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
#show.logo = #feed.itunes_image
#show.explicit = explicit_check(#feed.itunes_explicit)
#show.genre = #feed.itunes_categories
#show.tags = #feed.itunes_keywords
#show.url = #feed.url
#show.language = #feed.language
if #show.save
puts "Show import succeeded"
puts "#show.id: #{#show.id}"
else
puts "Show import failed"
end
# Episodes
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
#episode.release = item.published
#episode.image = item.itunes_image
#episode.explicit = explicit_check(item.itunes_explicit)
#episode.tags = item.itunes_keywords
#episode.url = item.enclosure_url
#episode.duration = item.itunes_duration
if #episode.save
puts "Episode import succeeded"
puts "#episode.show_id: #{#episode.show_id}"
else
puts "Episode import failed"
end
end
end
def explicit_check(string)
if string == "yes" || "Yes"
true
else
false
end
end
end
create_importer_spec.rb:
require "rails_helper"
RSpec.feature "Admins can create importers" do
let(:user) { FactoryGirl.create(:user, :admin) }
context "admins" do
before do
login_as(user)
visit "/"
click_link "Admin"
click_link "Importers"
click_link "New Importer"
end
scenario "with valid credentials" do
fill_in "Name", with: "The Stack Exchange Podcast"
fill_in "Url", with: "https://blog.stackoverflow.com/feed/podcast/" # Needs stubbing
select "iTunes", from: "Source"
click_button "Create Importer"
expect(page).to have_content "Importer added"
expect(page).to have_content "The Stack Exchange Podcast"
end
scenario "with invalid credentials" do
fill_in "Name", with: ""
fill_in "Url", with: ""
click_button "Create Importer"
expect(page).to have_content "Importer not added"
end
end
end
I think the episodes functionality in your SubscriptionImporter class is causing the problem...
#episodes = []
#feed.entries.each do |item|
#episodes.push(item) #-> each "#episodes" is a FeedJirra object
end
#episodes.each do |episode|
#-> you're now creating an episode in the same call as show, which will either mean that show is not persisted or perhaps some other error
end
I would personally limit the SubscriptionImporter functionality to only return data. You should be parsing that data through the respective models:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
if #import.save
#import.parse_show if #import.itunes?
end
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
def feed
return false unless itunes?
origin = Feedjirra::Feed.fetch_and_parse(self.url)
return {
name: origin.title,
description: origin.description,
logo: origin.itunes_image,
explicit: explicit_check(origin.itunes_explicit),
genre: origin.itunes_categories,
tags: origin.itunes_keywords,
url: origin.url,
language: origin.language,
entries: origin.entries
}
end
def parse_show
Show.create(feed)
end
def itunes?
self.source == "iTunes" #-> true/false
end
private
def explicit_check
string == "yes" || "Yes" #-> true/false
end
end
#app/models/show.rb
class Show < ActiveRecord::Base
has_many :episodes
attr_accessor :entries
after_create :create_episodes #-> might not persist entries
def create_episodes
if self.entries.any?
self.entries.each do |item|
self.episodes.create({
name: item.title
description: item.summary,
release: item.published,
image: item.itunes_image,
explicit: explicit_check?(item.itunes_explicit),
tags: item.itunes_keywords,
url: item.enclosure_url,
duration: item.itunes_duration
})
end
end
end
private
def explicit_check?
string == "yes" || "Yes"
end
end
The above will allow you to create an #importer, pull the feed from it, and populate Show & Episode models with the returned data.
Whilst this should resolve your issue, you need to consider OOP -- making each element an object.
Update
If you wanted to objectify this even more, there is a simple pattern to adopt:
Importer is all you need to save -- everything else should happen around this
Show + Episode could be the same class / table for all I know
With this in mind, you could do the following:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
#import.save
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/services/feed.rb
class Feed
attr_reader :params, :show, :episode, :origin
def initialize(params)
#params = params
end
def origin
#origin = Feedjirra::Feed.fetch_and_parse params[:source]
end
def show
#show = ShowHelper.new #origin
end
def episodes
#show.episodes
end
end
#app/services/show_helper.rb
class ShowHelper
attr_reader :origin
def initialize(origin)
#origin = origin
end
def name
#origin.title
end
def description
#origin.summary || #origin.description
end
def logo
#origin.itunes_image
end
def explicit
%r{^yes$} =~ #origin.itunes_explicit
end
def genre
#origin.itunes_categories
end
def tags
#origin.itunes_keywords
end
def url
#origin.url
end
def language
#origin.language
end
def episodes
#origin.entries
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
after_create :parse_show, if: "itunes?"
validates :source, :url, :name, presence: true
def itunes?
source == "iTunes"
end
def feed
#feed = Feed.new(self)
end
private
def parse_show
#show = Show.new(feed.show) if feed && feed.show
if #show.save && #show.entries.any?
#show.entries.each do |entry|
#show.episodes.create ShowHelper.new(entry)
end
end
end
end

Rails - Object values not being accessible on a attribute writer method

I have a Study model which have many fields, but I'm having troubles with 1
profesion_name
so in my study model I have this
class Study < ActiveRecord::Base
attr_accessible :profesion_related, :profesion_name
attr_accessor :profesion_related
def profesion_related=(id)
if id.present?
if self.study_type_id == 4
if self.country_id == 170
#some code here
else
profesion_parent = Profesion.find(id)
new_profesion = Profesion.create({g_code: profesion_parent.g_code, mg_code: profesion_parent.mg_code, name: self.profesion_name})
self.profesion = new_profesion
end
end
end
end
end
but I'm getting an error on the line that create a Profesion, because self.profesion_name is nil
if in my controller I do this
def create
#study = Study.new(params[:study])
respond_to do |format|
#here
puts #study.to_yaml
if #study.save
.....
end
I will see in the console that profesion_name has a value
but if I do this
class Study < ActiveRecord::Base
...
def profesion_related=(id)
puts self.to_yaml
....
end
end
I can see that self.profesion_name is empty
Why could this be happening?

Resources