0

I have a n00b question. I'm using Rails 5, and would like to have example data in the application. When a user creates a new project, the project should already contain sample "tasks" that the user can delete or edit.

I know I can use seeds.rb to create sample data in my development environment. What is the best way to do it in a production environment for new users, and how? Should I use seeds.rb, a migration, or a rake task?

Example controller:

  def create
    @project = Project.new(project_params)
    @project.user = current_user

    if @project.save
       // add sample content
       redirect_to @project 
    else
       render :new
    end
  end

In the Project model:

 belongs_to :user
 has_many :tasks, dependent: :destroy

When a new user joins and creates a new project, how do I add sample "tasks" automatically on the new project that the user creates?

UPDATE:

To create a task, I need a description and the current user's id (I'm using Devise, so I can use the current_user helper), for example:

@project.tasks.create!(description: "hello", user_id: current_user.id)
userden
  • 1,615
  • 6
  • 26
  • 50
  • A solution could be to create a service where you'll put the data for a sample project then, when a user is created, perform this service (with the created user in param). The user will be able to delete or edit this sample project. Also, when an already registered user will create a project, you'll be able to get the sample data and pass if to his new project if you want – cercxtrova Apr 02 '19 at 14:36

5 Answers5

3

You could build a simple ServiceObject that does the job. It allows you to keep your controller skinny and you can user your current_user Devise helper to keep track of which user is creating the project

if @project.save 
  SetupBaseProject.new(project).call
  redirect_to @project
else 
  # handle errors
end

In app/services/setup_base_project.rb

class SetupBaseProject 
  def initialize(project, user) 
    @project = project
  end

  def call
    # Create example tasks and any additional setup you want to add
    @project.tasks.create(description: 'Hello World', user: @project.user)
  end    
end 
Vincent Rolea
  • 1,601
  • 1
  • 15
  • 26
2

There are two possible scenarios considering your question.

  1. The first project created by a user needs to have sample tasks included by default
  2. Whenever a new project is created, sample tasks are created by default. Irrespective of the user is new user/existing user.

For first scenario, We need to track whether project is created by new user by adding a boolean field to user, for example: new_user which defaults true. We can use active record callbacks for generating sample tasks after project is created.

For Example, Project Model :

belongs_to :user
has_many :tasks, dependent: destroy
after_create :generate_tasks
def generate_tasks
   if self.user.new_user #This conditional block can be modified as needed
     (1..3).each do |n|
       self.tasks.create!(description: "Sample task #{n}", user_id: self.user.id)
     end
   end
end

For the second scenario, We can use the same projects model file and just remove the conditional statement which will help create sample tasks by after project is created.

If you need any clarification, please comment out.

dharmesh
  • 452
  • 4
  • 12
  • Thanks @dharmesh! I have one problem, in my task I have to add also a user_id, but I can't use the devise current_user helper in a model. Maybe the after_create logic should be in the controller? I'll update the question. – userden Apr 11 '19 at 16:28
  • @userden I have understood your question clearly now, This can be done using active record callbacks itself. I have updated my answer to achieve that. Please verify the updated answer. Let me know in case of any other clarification.! – dharmesh Apr 11 '19 at 16:44
  • @userden, Have you tried this out? let me know if you need any clarifications on my answer. – dharmesh Apr 12 '19 at 16:43
  • Yes, it works, and I like it. I was just waiting and thinking what is the "best" or "proper" way to do this? I also like the idea of using a simple ServiceObject, what Vincent suggested in his answer. Anyways, I'll approve your answer soon, I just test one more thing. – userden Apr 12 '19 at 17:56
  • Thanks @dharmesh, sorry it took a while for me to get back! – userden Apr 15 '19 at 19:09
1

I've done this quite a few times in the past. From my experience, you eventually have to give other people the ability to manage those defaults (product owners, marketing, etc)

What I've done in the past is to have a test user with a project that acts as 'the default' project.

Whenever anyone wants to create a new project, you clone it.

I used https://github.com/amoeba-rb/amoeba for that. It offers out of the bow way to override attributes that I'd want to change and can cascade the cloning to any associations you'd want to clone.

khaled_gomaa
  • 3,382
  • 21
  • 24
0

Say sample data is on model Detail which was populated with seeds.rb and belongs to 'Project'. You can dup that record and asign it to the new project (not tested):

def create
    @project = Project.new(project_params)
    @project.user = current_user
    @project.details << Detail.find_by_name('sample').dup

    if @project.save 
       redirect_to @company 
    else
       render :new
    end
  end

Also, consider use a transaction when saving data on more than one model.

CAmador
  • 1,881
  • 1
  • 11
  • 19
0

Full disclosure, I work in Rails 4...

If it were me, I would use FactoryBot to get the dummy data you want. Factories are great for testing so if you use them for testing, why not borrow them for this? This post shows an example where someone wanted to mock dummy data in console, same ideas could apply for you here.

Once you've got your factories mocked up... maybe for tasks something like:

require 'faker'
FactoryBot.define do
  factory :task do
    transient do
      parent_project { nil }
    end

    description { Faker::Hacker.say_something_smart }
    project_id { parent_project.id }
  end
end

Maybe create a method in the project model like:

def create_dummy_data
  require 'factory_bot'
  require 'faker'
  include FactoryBot::Syntax::Methods

  # create_list will spit out 3 tasks associated with your project
  create_list(:task, 3, parent_project: self)
end

Then in your example: after calling save...

if @project.save
   @project.create_dummy_data  
   redirect_to @company 
else

I can't think of a reason you couldn't go this route... noodling around in console I didn't have any problems, but I'd look at this answer as a starting point and not a final solution =P

Stephen
  • 337
  • 2
  • 7