Here is my problem...I am setting up a game where several users can play a game with 10,000 raffle tickets. Every game will have 10,000 raffle tickets and will not start until all 10,000 raffle tickets are sold. That being said, I have two simple classes in my DB, and for every game that goes, I need to initialize the 10,000 unique tickets for a single game relationship. I'm not sure where I'm going wrong here. Any help would be greatly appreciated.
models/game.rb
class Game < ActiveRecord::Base
has_many :tickets
end
models/ticket.rb
class Ticket < ActiveRecord::Base
belongs_to :game
end
controllers/games_controller.rb
class GamesController < ApplicationController
def create
g = Game.new
g.winning_ticket_num = params["winning_ticket_num"]
g.value_per_ticket = params["value_per_ticket"]
g.save
10000.times do
ticket = Ticket.new
ticket.game_id = g.id
ticket.nickname = "null"
ticket.save
end
end
end
controllers/tickets_controller.rb
class TicketsController < ApplicationController
def create
t = Ticket.new
t.nickname = params["nickname"]
t.game_id = params["game_id"]
t.save
end
end
After all the comments exchanged, let's summarize:
class GamesController < ApplicationController
def create
#game = Game.new(params[:game])
10000.times do
#game.tickets.build(nickname: "null")
end
#game.save
end
end
Game.new creates your game, based on the parameters in your view. The "winning_ticket_num" and "value_per_ticket" will be automatically "copied" to your new game object. You must make sure these parameters can be assigned to, either using strong parameters in Rails, or using attr_accessible in Rails < 4.0
The #game.tickets.build creates the 10000 tickets. The game's ID will be automatically assigned when the game is finally saved. Also the tickets themselves will be saved when the parent game is saved
The thing i see that is wrong, If you are using form_for, then the params are params[:game]
class GamesController < ApplicationController
def create
g = Game.new
g.winning_ticket_num = params[:game]["winning_ticket_num"]
g.value_per_ticket = params[:game]["value_per_ticket"]
g.save
10000.times do
ticket = Ticket.new
ticket.game_id = g.id
ticket.nickname = "null"
ticket.save
end
end
end
So adding params[:game] will get the values
Don't set the IDs yourself, use ActiveRecord associations. Note that tickets.create will save the Ticket object to the database when it is created (which seems what you wanted to do above).
def create
#g = Game.new
#g.winning_ticket_num = params["winning_ticket_num"]
#g.value_per_ticket = params["value_per_ticket"]
#g.save
10000.times do
#g.tickets.create(nickname => "null")
end
end
EDIT: syntax error, the = changed to =>. Also remove the ticket. part. Sorry, this is what happens without tests.
Related
Here is my issue. I have two models (Construction and Customer)
class Construction < ApplicationRecord
has_many :works
belongs_to :customer
end
class Customer < ApplicationRecord
has_many :constructions
end
I would like to associate a Customer to a Construction during the creation of a new construction.
To do so I have de following controller's method (which is obviously false)
def create
# #construction = Construction.new(constructions_params) (commented)
#construction = Construction.new(customer: #customer)
#customer = Customer.find(params[:customer_id])
#construction.save!
end
from the params I am able to understand that the construction is not saved because it is not attached to a customer and so cannot be created.
I am new to rails and I have been struggling for hours now..
Hope someone will be able to help me.
thanks a lot
Try to revert the order:
#customer = Customer.find(params[:construction][:customer_id])
#construction = Construction.new(customer: #customer)
#construction.save!
you need to assign #customer instance variable before you use it. Otherwise it's nil and nothing is assigned to the new Construction record.
If you have the customer_id available at the point of form creation I reckon that you can do something like this.
Also given the belongs_to relations with the customer on the construction, you should be able to update the customer_id on the construction.
def create
#construction = Construction.new(construction_params)
if #construction.save
# whatever you want to do on success
else
# Whatever you want to do on failure
end
end
# Given you have construction params
private
def construction_params
params.require(:construction).permit(:all, :the, :construction, :attributes, :customer_id)
end
Ruby 2.1
Rails 4.2
I need to generate (by concatenation) a code (which contains the product id) on new records.
The problem is that the record must be created to have a id. So I can't generate the code on "create" because de id don't exists yet. I'm stuck.
So far it works, but only on update.
class Product < ActiveRecord::Base
before_save :generate_code
private
def generate_code
tag = tags.map(&:name).join(", ").first(2)
self.code = ("#{category_id} #{tag} #{id} #{glaze.code}").parameterize
end
end
How to create a new record and, at the same time, concatenate your id on "code"?
Update:
I need it on update too.
The final solution is:
class Product < ActiveRecord::Base
after_save :generate_code
private
def generate_code
tag = tags.first.name.first(2)
update_column(:code, ("#{category_id}" + "#{tag}" + "#{id}" + "#{glaze.code}").parameterize.upcase)
end
end
You could Try something like this. This will generate a new code after creation (Part 1 issue where you have no id) and before update (Part 2 issue which works fine as a before because you already have an id)
class Product < ActiveRecord::Base
after_create :generate_code
before_update :generate_code
private
def generate_code
tag = tags.map(&:name).join(", ").first(2)
if changed?
self.code = "#{category_id} #{tag} #{id} #{glaze.code}"
else
update_attribute(:code,"#{category_id} #{tag} #{id} #{glaze.code}")
end
end
end
As #MohammadAbuShady mentioned this should also work as it is a direct DB edit with no callbacks or validations
class Product < ActiveRecord::Base
after_save :generate_code
private
def generate_code
tag = tags.first.name.first(2)
update_column(:code, "#{category_id} #{tag} #{id} #{glaze.code}")
end
end
i have a question about associations in rails. The situation is the following:
Models:
class Character < ActiveRecord::Base
has_one :character_stats
end
class CharacterStats < ActiveRecord::Base
belongs_to :character
end
Now i need to create stats when a new character is created.
What i doo is this at the moment, i feel like this is a workaround with rails. Is there a more "raily" way to do this?
after_save :character_init
def character_init
create_stats
end
def create_stats
stats = CharacterStats.new
stats.character_id = self.id // this bothers me!
stats.save
end
But i feel there should be something like this:
stats.character << self
Thank You in advance :)
EDIT:
here is how my model look in real life:
def create_stats
race_stats = Race.find(race_id).race_stats
class_stats = RaceClass.find(race_class_id).class_stats
stats = CharacterStats.new
stats.character_id = self.id
stats.health = race_stats.health + class_stats.health
stats.mana = race_stats.mana + class_stats.mana
stats.intellect = race_stats.intellect + class_stats.intellect
stats.armor = race_stats.armor + class_stats.armor
stats.magic_resist = race_stats.magic_resist + class_stats.magic_resist
stats.attack = race_stats.attack + class_stats.attack
stats.defence = race_stats.defence + class_stats.defence
stats.save
self.character_stats_id = stats.id
end
first of all if you want to create CharacterStats after Character is created use after_create callback. About your question you can use ActiveRecord methods which looks like create_character_stats for creating model and build_character_stats for initializing model. So you can change your create_stats method to something like this
def create_stats
self.create_character_stats
end
or you can change your after callback to this
after_create :create_character_stats
and then you don't need any method for this, but in this case you don't have ability to pass attributes to model creating.
also this question mentioned here Using build with a has_one association in rails
in rails way you can use the build_association method refer doc. Also the association should be created only when parent object is created so use after_create callback
class Character < ActiveRecord::Base
has_one :character_stats
after_create :create_stats
def create_stats
state = self.build_character_stats({}) # empty hash as you are not passing any attributes
state.save
end
end
Also, the model name CharacterStats looks plural and that can violate the naming convention and can cause some issue like resolving association class on run time and so.
In my Rails app I have users who can have many payments.
class User < ActiveRecord::Base
has_many :invoices
has_many :payments
def year_ranges
...
end
def quarter_ranges
...
end
def month_ranges
...
end
def revenue_between(range, kind)
payments.sum_within_range(range, kind)
end
end
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :items
has_many :payments
...
end
class Payment < ActiveRecord::Base
belongs_to :user
belongs_to :invoice
def net_amount
invoice.subtotal * percent_of_invoice_total / 100
end
def taxable_amount
invoice.total_tax * percent_of_invoice_total / 100
end
def gross_amount
invoice.total * percent_of_invoice_total / 100
end
def self.chart_data(ranges, unit)
ranges.map do |r| {
:range => range_label(r, unit),
:gross_revenue => sum_within_range(r, :gross),
:taxable_revenue => sum_within_range(r, :taxable),
:net_revenue => sum_within_range(r, :net) }
end
end
def self.sum_within_range(range, kind)
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount")
end
end
In my dashboard view I am listing the total payments for the ranges depending on the GET parameter that the user picked. The user can pick either years, quarters, or months.
class DashboardController < ApplicationController
def show
if %w[year quarter month].include?(params[:by])
#unit = params[:by]
else
#unit = 'year'
end
#ranges = #user.send("#{#unit}_ranges")
#paginated_ranges = #ranges.paginate(:page => params[:page], :per_page => 10)
#title = "All your payments"
end
end
The use of the instance variable (#sum) greatly reduced the number of SQL queries here because the database won't get hit for the same queries over and over again.
The problem is, however, that when a user creates, deletes or changes one of his payments, this is not reflected in the #sum instance variable. So how can I reset it? Or is there a better solution to this?
Thanks for any help.
This is incidental to your question, but don't use #select with a block.
What you're doing is selecting all payments, and then filtering the relation as an array. Use Arel to overcome this :
scope :within_range, ->(range){ where date: range }
This will build an SQL BETWEEN statement. Using #sum on the resulting relation will build an SQL SUM() statement, which is probably more efficient than loading all the records.
Instead of storing the association as an instance variable of the Class Payment, store it as an instance variable of a user (I know it sounds confusing, I have tried to explain below)
class User < ActiveRecord::Base
has_many :payments
def revenue_between(range)
#payments_with_invoices ||= payments.includes(:invoice => :items).all
# #payments_with_invoices is an array now so cannot use Payment's class method on it
#payments_with_invoices.select { |x| range.cover? x.date }.sum(&:total)
end
end
When you defined #sum in a class method (class methods are denoted by self.) it became an instance variable of Class Payment. That means you can potentially access it as Payment.sum. So this has nothing to do with a particular user and his/her payments. #sum is now an attribute of the class Payment and Rails would cache it the same way it caches the method definitions of a class.
Once #sum is initialized, it will stay the same, as you noticed, even after user creates new payment or if a different user logs in for that matter! It will change when the app is restarted.
However, if you define #payments_with_invoiceslike I show above, it becomes an attribute of a particular instance of User or in other words instance level instance variable. That means you can potentially access it as some_user.payments_with_invoices. Since an app can have many users these are not persisted in Rails memory across requests. So whenever the user instance changes its attributes are loaded again.
So if the user creates more payments the #payments_with_invoices variable would be refreshed since the user instance is re-initialized.
Maybe you could do it with observers:
# payment.rb
def self.cached_sum(force=false)
if #sum.blank? || force
#sum = includes(:invoice => :items)
end
#sum
end
def self.sum_within_range(range)
#sum = cached_sum
#sum.select { |x| range.cover? x.date }.sum(&total)
end
#payment_observer.rb
class PaymentObserver < ActiveRecord::Observer
# force #sum updating
def after_save(comment)
Payment.cached_sum(true)
end
def after_destroy(comment)
Payment.cached_sum(true)
end
end
You could find more about observers at http://apidock.com/rails/v3.2.13/ActiveRecord/Observer
Well your #sum is basically a cache of the values you need. Like any cache, you need to invalidate it if something happens to the values involved.
You could use after_save or after_create filters to call a function which sets #sum = nil. It may also be useful to also save the range your cache is covering and decide the invalidation by the date of the new or changed payment.
class Payment < ActiveRecord::Base
belongs_to :user
after_save :invalidate_cache
def self.sum_within_range(range)
#cached_range = range
#sum ||= includes(:invoice => :items)
#sum.select { |x| range.cover? x.date }.sum(&total)
end
def self.invalidate_cache
#sum = nil if #cached_range.includes?(payment_date)
end
Let's say I have a simple model:
class Subscription < ActiveRecord::Base
attr_accessor :subscribed
def subscribed
#subscribed = Subscription.where(task_id, user_id)
!#subscribed.empty?
end
end
I'd like to make sure I can take a #subscription instance and call #subscription.subscribed - at the moment it can't find the method.
I think you are doing something like:
#subscription = Subscription.where(task_id, user_id)
#subscription.subscribed
instead of something like this:
#subscription = Subscription.where(task_id, user_id).first
#subscription.subscribed
It's just a guess.
I believe the problem is your where call. Try this:
class Subscription < ActiveRecord::Base
belongs_to :task
belongs_to :user
attr_accessor :subscribed
def subscribed?
#subscribed ||= !Subscription.where(:task_id => task.id, :user_id => user.id).empty?
end
end
If I'm correctly understanding what you're trying to do I think that should work.
But it still doesn't make a lot of sense to me to have this method on the Subscription model. If a subscription exists wouldn't that mean that the user is subscribed automatically. Was this meant to be a class method mabye? I'd think user.subscribed_to? issue or Issue.has_subscriber? user would be a better place to handle this.
If I understand your problem, you can do:
class Subscription < ActiveRecord::Base
attr_accessor :subscribed
def subscribed?
#subscribed = Subscription.where(task_id, user_id)
!#subscribed.empty?
end
end
and you can do (a.i):
s = Subscription.first
s.subscribed? # true or false
s.subscribed # the object (#subscribed)
From your error in the comments, your object called #subscription is not pointing to an instance of Subscription but instead a relation.
Probably you did something like
#subscription = Subscription.where(... some condition ...)
which returns a relation, which you can access as an array. So call .first to get the first element out of it. Or use Subscription.find.
Hope this helps.