Nischay Namdev
Nischay Namdev

Reputation: 593

How to pass dynamic params in Rails?

I want some of my model attributes to predefined dynamically. I have various models.And now I want My Bill model to create objects using other model instances.

Models :

    leave.rb        # belongs_to :residents
    resident.rb     # has_many:leaves,has_many:bills,has_one:account
    bill.rb         # belongs_to:residents
    rate_card.rb    # belongs_to:hostel
    account.rb      # belongs_to:resident
    hostel.rb       

now here is my bills controller create method :

def create
    @bill = Resident.all.each { |resident| resident.bills.create(?) }
    if @bill.save
      flash[:success]="Bills successfully generated"
    else
      flash[:danger]="Something went wrong please try again !"
    end
  end

I want to build bill using all of the models eg:

resident.bills.create(is_date:using form,to_date:using form,expiry_date:using form,amount:30*(resident.rate_card.diet)+resident.rate_card.charge1+resident.rate_card.charge2)+(resident.account.leaves)*10+resident.account.fine)
///////Is this possible ?

And how to use strong params here ?

Pls help me out thxx..

Upvotes: 3

Views: 3268

Answers (4)

Rodrigo Martinez
Rodrigo Martinez

Reputation: 711

I think the Rails way for this logic you want is with callbacks if you want calculated attributes either on create, update or delete, meaning attributes that depend on other models. For instance:

class Bill < ActiveRecord::Base
  ...
  before_create :set_amount

  ...

  protected

    def set_amount
      self.amount = 30 * self.resident.rate_card.diet + self.resident.rate_card.charge1 + self.resident.rate_card.charge2 + (self.resident.account.leaves) * 10 + self.resident.account.fine
    end
end

If you want this logic to be used when updating the record also, then you should use before_save instead of before_create.

After you do this, you should accept the usual params (strong) of Bill model, as in:

def bill_params
  params.require(:bill).permit(:is_date, :to_date, :expiry_date)
end

So your create call would be like:

resident.bills.create(bill_params)

Also, be wary of your create action, you should probably create a method either on your Bill or your Resident model that uses transactions to create all bills at the same time because you probably want either every bill created or none. This way you won't have the Resident.all.each logic in your BillsController.

Upvotes: 1

Lanny Bose
Lanny Bose

Reputation: 1857

I'm confused at your syntax of your controller. @bill is being set to the value of a loop, which feels off. Each loops return the enumerable you cycle through, so you'll end up with @bill = Resident.all with some bills being created on the side.

What your controller really wants to know is, did my many new bills save correctly?

This seems like a perfect place to use a ruby object (or, colloquially, a Plain Old Ruby Object, as opposed to an ActiveRecord object) to encapsulate the specifics of this bill-generator.

If I'm reading this right, it appears that you are generating many bills at once, based on form-inputted data like:

  • is_date
  • to_date
  • expiry_date

...as well as some data about each individual resident.

Here's the model I'd create:

app/models/bill_generator.rb

class BillGenerator
  include ActiveModel::Model
  # This lets you do validations

  attr_accessor :is_date, :to_date, :expiry_date
  # This lets your form builder see these attributes when you go form.input

  attr_accessor :bills
  # ...for the bills we'll be generating in a sec

  validates_presence_of :is_date, :to_date, :expiry_date
  # You can do other validations here. Just an example.

  validate :bills_are_valid?

  def initialize(attributes = {})
    super # This calls the Active Model initializer
    build_new_bills # Called as soon as you do BillGenerator.new
  end

  def build_new_bills
    @bills = []
    Resident.all.each do |r|
      @bills << r.bills.build(
        # Your logic goes here. Not sure what goes into a bill-building...
        # Note that I'm building (which means not-yet-saved), not creating
      ) 
    end

  def save
    if valid?
      @bills.each { |b| b.save }
      true
    else
      false
    end
  end

  private

    def bills_are_valid?
      bill_validity = true
      @bills.each do |b|
        bill_validity = false unless b.valid?
      end
      bill_validity
    end

end

Why all this mess? Because in your controller you can do...

app/controllers/bill_controller.rb

def create
  @bill_generator = BillGenerator.new(bill_generator_params)
  if @bill_generator.save?
     # Redirect to somewhere with a flash?
  else
     # Re-render the form with a flash?
  end
end

def bill_generator_params
  params.require(:bill_generator).permit(:is_date, :to_date, :expiry_date)
  # No extra garbage. No insecurity by letting all kinds of crud through!
end

...like a BillGenerator is any old object. Did it save? Great. It didn't, show the form again.

Now, my BillGenerator won't just be copy-and-paste. Your 'build_new_bills' probably will have some of that math you alluded to, which I'll leave to you.

Let me know what you think!

Upvotes: 1

Vasfed
Vasfed

Reputation: 18444

create takes a hash, you can:

create_params = { amount: 30*(resident.rate_card.diet) }
create_params[:some_field] = params[:some_field]
# and so on
resident.bills.create(create_params)

or:

obj = resident.bills.build(your_strong_parameters_as_usual)
obj.amount = # that calculation
obj.save!

Upvotes: 1

ddavison
ddavison

Reputation: 29032

you can do it by using params.permit! as this allows any parameters to be passed. here's an example:

def create
  ...
  @bill = Resident.all.each { |resident| resident.bills.create(any_params) }
end

private
def any_params
  params.permit!
end

be careful with this of course, as you are opening this up to potential exploits.

Upvotes: -1

Related Questions