Joe Morano
Joe Morano

Reputation: 1875

How can I dynamically generate parameters for a create method?

I have a model "Item", and I want to generate a bunch of Items with data taken from a CSV file. But I want to allow the user to select which data columns are used.

So my model "User" has a json attribute called Columns, telling which columns should be used from that user's CSV file. For example, if user.columns == {"title"=>"true","category"=>"false"}, then the Title column should be used but the Category column shouldn't. (Alternatively, I could only list the columns I want to include, like this: {"title"=>"included"}, and later do something like user.columns.each {|c| c[0]} to get an array of all the included columns.)

I have a method for generating Items based on the CSV data:

def create
  #...
  SmarterCSV.process(file).each do |row| #this line returns an array of all the rows in the CSV file
    Item.create(title: row[:title], category: row[:category])
  end
end

but how can I modify the method's parameters based on the contents of user.columns? For the {"title"=>"true","category"=>"false"} example, the method would just be Item.create(name: row[:title]). Is it possible to dynamically generate a method like that?

I'm planning to have quite a lot of possible columns, so doing an if conditional for every possibility is unfeasible.

Upvotes: 1

Views: 155

Answers (2)

Mark Thomas
Mark Thomas

Reputation: 37507

I would add a method to your User model to return the column names as symbols:

class User
  def selected_columns
    columns.select{|_,v| v == "true"}.keys.map(&:to_sym)
  end
end

and then modify your item creation like so:

Item.create(row.slice(*user.selected_columns))

Upvotes: 1

gwcodes
gwcodes

Reputation: 5690

Item.create(name: row[:name]) also takes a hash, and can be written equivalently as Item.create({ name: row[:name] })

As such - you can build up the entire object in a hash each time; then slice off any attributes that you don't want, and then pass to create. So assuming you have:

user.columns
#=> {"name"=>"true","category"=>"false"}

Then you could write:

user_object = { "name" => row[:name], "category" => row[:category] }
#=> include all the possible attributes here

included_attributes = user.columns.select { |k, v| v == "true" }
#=> {"name"=>"true"}

Item.create( user_object.slice(*included_attributes.keys) )
#=> `Item` is created with only `name` attribute

Edit: as engineersmnky points out in the comments, row is already a hash. This simplifies it further, and you could instead just write:

SmarterCSV.process(file).each do |row|
  included_attributes = user.columns.select { |k, v| v == "true" }
  Item.create( row.slice(*included_attributes.keys) )
end

Upvotes: 2

Related Questions