using .dup with multiple records - ruby-on-rails

I am creating a Stripe payment engine for the Catarse project. I have three records I need to copy from my User, who is a project owner to my User's project. But because I'm a beginner, the code looks like S&%#te!
#project_controller.rb
def create
...
check_for_stripe_keys
....
end
def show
...
check_for_stripe_keys
....
end
....
def check_for_stripe_keys
if #project.stripe_userid.nil?
#project.reload
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
elsif #project.stripe_userid != #project.user.userid
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
end
#project.save
end
....
I only need those three records because my stripe code is an engine. Three things:
1) Initially I thought to user update_attributes but I don't know if its possible to use .dup in that method.
2) Is it possible to put this in a helper located in the engine thats accessible to the main_app so users don't have to edit the main_app project_controller code?
3) Is there a cleaner way to show the above .dup code? 'Cause it's fugly!
Thanks for your help!

This is a more compact way to write what you already have:
[:stripe_access_token, :stripe_key, :stripe_userid].each do |field|
#project.send("#{field.to_s}=", #project.user.send(field).dup)
end

Maybe something like this:
Gemfile:
gem 'deep_cloneable'
Code:
#user.dup :include => [:stripe_userid, :stripe_access_token, :stripe_key]
I didn't quite follow your relationships. But take a look at this answer: What is the easiest way to duplicate an activerecord record?
Here is deep_cloneable:
https://github.com/moiristo/deep_cloneable

Related

Rails association collection build if not exist

I have three models Exam, User and ExamResult. ExamResult contains the records for all the students (User) for exams (Exam). For one particular Exam record, there should be one record in ExamResult for each student. In the edit method of ExamController, depending if the ExamResult record has been created for one student, I need to build one new record or just skip it. Not sure if this is idiomatic Rails way of doing it.
# ExamController
def edit
User.students.each do |s|
#exam.exam_results.build(user_id: s.id) unless #exam.exam_results.find_by(user_id: s.id)
end
end
or this way:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
Maybe neither is idiomatic Rails. Any suggestions are welcome.
Edit
Bring find_or_initialize_by (recommended by #user3334690) on the table. If I understand the doc correctly, this should do the same as previous two implementations.
def edit
User.students.each do |s|
#exam.exam_results.find_or_initialize_by(user_id: s.id)
end
end
Instead of build you can use find_or_create_by_user_id in above case.
def edit
User.students.each do |s|
ExamResult.find_or_create_by_exam_id_and_user_id(exam_id, user_id)
end
end
there is this way:
def edit
User.students.each do |s|
ExamResult.where(exam_id: #exam.id, user_id: s.id).first_or_create
end
end
Use your second example:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
As I said in comments above this will query the database twice regardless of the number of students which will scale much better. If you're in a really extreme environment you can write it to use only one query along with "pluck" to pull only the "id" column (and avoid the object creation overhead) like this:
newIds = User.students.where("users.id not in (select user_id from exam_results where exam_id=?)", #exam.id).pluck(:id)
However, the readability is reduced for that. Your original would also benefit from using "pluck" instead of "map".
One other style note - I would use "new_ids" which is the standard idiomatic way of doing it with Rails.

Eager Loading with Rails Query

In my rails app, projects have many steps, and steps can have questions
I'd like to write a method that checks whether a project has any questions and return the id of the step with the question.
Currently, I have the following in my project.rb
def step_with_question
question_step = ""
steps.order(:published_on).each do |step|
if step.question
question_step = step.id
end
end
return question_step
end
But I think this is inefficient and think there is probably a much faster way to do this with eager loading (this creates a query for every step in the project). Does anyone have advice for how to do this?
You can use joins to only return associated :steps which actually have :questions associations with them:
#project = Project.joins(steps: :questions).order('steps.published_on').find(id)
This query will return only the project steps that actually have an associated question. You can now safely loop through the steps records and return or use step.id
#project.steps.each do |step|
question_step = step.id
# do something with the question_step
end
I didn't understand what your code does exactly, but if you want to access a Question, from a Step, you can use the method includes:
project = Project.find(id) # Get a product just to show how it works
# To tell Rails to make a single query when you want to
# access the questions, do something like this:
steps_with_questions = project.steps.includes(:question)
This way, when you try to access a question, it'll be already loaded.
The best way to work with these is to write a scope for step.rb, like this:
scope :with_questions, lambda { includes :questions }
Now you only need to call:
project.steps.with_questions
Making the code a lot easier to read.
EDIT: Your code would look like this: (without the scope I mentioned earlier)
def step_with_question
question_step = ""
steps.order(:published_on).includes(:question).each do |step|
if step.question
question_step = step.id
end
end
return question_step
end

Can I do Find_Or_Create in model before save?

So i'm looking to move a find_or_create function from my controller to my model. Bascially, if the location already exists then choose that, if not then create a new one. From a bit of reading, I think a before save function should do it, but I'm not sure on the correct syntax and can't seem to find many examples anywhere.
Location.rb
before_save :get_locations
def get_locations
Location.find_or_create_by(name: [:name])
end
Here's my controller; it was working fine when running the find_or_create here.
Locations_controller.rb
def create
#location = Location.new(location_params)
# == worked previously == #
# #location = Location.find_or_create_by(name: location_params[:name])
# == worked previously == #
respond_to do |format|
...
end
end
Help would be great!
First I'll try to guess, what is that you want to do there. Considering what you've given us, you are trying to prevent the creation of several locations with the same name and if a user tries to do that, find him the already created location instead of creating a new one.
If that's true, then there're couple things to mention:
You don't need any before_create methods there. All the model needs to have is a validates_uniqueness_of :name call so there would never be 2 locations with the same name.
You need to move that creation logic back into the controller. You can use the find_or_initialize_by(name:location_params[:name]) call (in case you want to do something with the found record afterwards) or find_or_create_by(name:location_params[:name]) (to create it right away).

How to fetch a random record in rails?

I am trying to fetch a random record in rails, to render in my home page.
I have a post model with content and title attributes. Lets say i wanted to fetch a random post(content and title) for some reason, How can i go about it in ruby. Thanks in advance.
You might find this gem handy : Faker
It allows to generate random strings with some meaning.
For example, a name :
Faker::Name.name => “Bob Hope”
Or an e-mail
Faker::Internet.email
In addition to this gem, if you want to be able to generate mock models very easily, I recommend the gem Factory Girl
It allows you to create factories for your model, sou you can generate a model with random attributes quickly.
Posting another answer since the first one answered to an unclear question.
As #m_x said, you can use RANDOM() for SQL.
If you don't mind loading all the dataset, you can do it in ruby as well :
Post.all.sample
This will select one random record from all Posts.
I know this is an old question, but since no answer was chosen, answering it might be helpful for other users.
I think the best way to go would be generating a random offset in Ruby and using it in your Active Record statement, like so:
Thing.limit(1).offset(rand(Thing.count)).first
This solution is also performant and portable.
In your post controller,
def create
#post = Post.new(params[:post])
if you_want_some_random_title_and_content
title_length = 80 #choose your own
content_length = 140 #choose your own
#post.title = (0...title_length).map{(65+rand(26)).chr}.join
#post.content = (0...content_length).map{(65+rand(26)).chr}.join
end
if #post.save
redirect_to #post
else
render 'new'
end
end
Using Kent Fedric's way to generate random string
unfortunately, there is no database-agnostic method for fetching a random record, so ActiveRecord does not implement any.
For postgresql you can use :
Post.order( 'RANDOM()' ).first
To fetch one random post.
Additionnally, i usually create a scope for this:
class Post < ActiveRecord::Base
scope :random_order, ->{ order 'RANDOM()' }
end
so if you change your RDBMS, you just have to change the scope.

Rails: activeadmin overriding create action

I have an activeadmin resource which has a belongs_to :user relationship.
When I create a new Instance of the model in active admin, I want to associate the currently logged in user as the user who created the instance (pretty standard stuff I'd imagine).
So... I got it working with:
controller do
def create
#item = Item.new(params[:item])
#item.user = current_curator
super
end
end
However ;) I'm just wondering how this works? I just hoped that assigning the #item variable the user and then calling super would work (and it does). I also started looking through the gem but couldn't see how it was actually working.
Any pointers would be great. I'm assuming this is something that InheritedResources gives you?
Thanks!
I ran into a similar situation where I didn't really need to completely override the create method. I really only wanted to inject properties before save, and only on create; very similar to your example. After reading through the ActiveAdmin source, I determined that I could use before_create to do what I needed:
ActiveAdmin.register Product do
before_create do |product|
product.creator = current_user
end
end
Another option:
def create
params[:item].merge!({ user_id: current_curator.id })
create!
end
You are right active admin use InheritedResources, all other tools you can see on the end of the page.
As per the AA source code this worked for me:
controller do
def call_before_create(offer)
end
end

Resources