Rails double-entry method - ruby-on-rails

1-account.rb
class Account < ActiveRecord::Base
belongs_to :transaction
end
class Supplier < Account
end
class Expense < Account
end
2-transaction.rb
class Transaction < ActiveRecord::Base
has_many :accounts
accepts_nested_attributes_for :accounts
end
3-migration schema
create_table "accounts", :force => true do |t|
t.string "name"
t.decimal "debit"
t.decimal "credit"
t.decimal "balance"
t.string "type"
t.integer "transaction_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "transactions", :force => true do |t|
t.string "name"
t.decimal "amount"
t.date "date"
t.string "document"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Question1:
What's the best method to reach supplier and expense in the view (see the picture below)?
Question2:
How can I implement a method that automatically record the transaction amount in expense_debit and supplier_credit, and vice versa? (View screenshot)

I'd suggest an alternative setup. For one thing, your transaction does not record which is the "from" account and which is the "to" account. That's kind of important to know for later.
I recognise that there may be multiple accounts from and to... but debit vs credit should be recorded on a per-transaction basis, not in just one big lump on the Account, otherwise it'll be difficult later to, say, list the change in an account's value between date A and date B (important for tax-returns or quarterly sales reports or whatever).
i don't know about naming, but perhaps a transaction has_many account-transfers and a transfer is "transaction_id, account_id, amount, direction" (where direction is debit/credit).

Related

Rails Model Associations when nesting - Confusion

Perhaps I am approaching this all wrong, I have the following models setup:
The User model has many Questions, a Question belongs_to a User
This means I can easily do: u = User.last.questions and see all of the questions associated with the user last added.
Following this, I have a Payment model that belongs_to a Question. A Question has_one payment meaning I can easily do u = User.last.questions.last.payment, getting the payment details from the question last added by the last added user (daft example perhaps!).
Now the bit that is confusing me
I have a model called PaymentOption which is used to populate a drop down list, for different prices, the PaymentOption belongs_to a Payment and a Payment has_one PaymentOption
This is all working correctly, but the only way for me to find out the details of the payment option, such as the amount, would be to do something like this:
payment_amount = PaymentOption.find(User.last.questions.last.payment.payment_option_id).amount
Ideally I would do something like this:
amount = User.last.questions.last.payment.payment_option.amount
By doing it in the way I have, it is as good as doing two seperate queries, one to get the payment option ID and another to find a detail relating to that particular payment option. Surely there is a better way to approach this?
Thanks in advance
EDIT 1
To clarify my answer, I have included the schema for Payments and PaymentOptions
create_table "payment_options", force: :cascade do |t|
t.integer "amount"
t.text "description"
t.string "title"
t.boolean "user_accessible", default: true
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "payments", force: :cascade do |t|
t.string "email"
t.string "token"
t.integer "question_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "payment_option_id"
end
add_index "payments", ["payment_option_id"], name: "index_payments_on_payment_option_id"
add_index "payments", ["question_id"], name: "index_payments_on_question_id"
I think here is you problem:
I have a model called PaymentOption which is used to populate a drop
down list, for different prices, the PaymentOption belongs_to a
Payment and a Payment has_one PaymentOption
You said you've setup Payment has_one PaymentOption and PaymentOption belongs_to Payment. But in the schema described in the end Payment has a payment_option_id.
In this case, Payment belongs_to PaymenteOption and not the opposite. See this example in the Rails Guide. Account has supplier_id so Supplier has one Account and Account belongs_to Supplier.
Your models should be like this:
class Payment < ActiveRecord::Base
belongs_to :payment_option
end
class PaymentOption < ActiveRecord::Base
has_one :payment # Or even has_many :payments
end

How to set up my model-relations properly

I have 3 models: Hacks, Votes, Users.
A user can create many hacks.
Each user should be able to vote on each hack ONCE (Rating of 1-5. Rating should be updateable in case of a missclick or whatever).
I thought about the following relations:
Hack.rb
belongs_to :user
User.rb
has_many :hacks
Votes.rb
belongs_to :user
belongs_to :hack
Is that correct or am I missing something?
I thought about getting all the votes like this later on:
Hack.first.votes
What kind of foreign-keys do I have to set up?
In my schema.rb I already successfully set my users<=>hack relation up, without any foreign keys.
ActiveRecord::Schema.define(version: 20141019161631) do
create_table "hacks", force: true do |t|
t.string "url"
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "email", null: false
t.string "crypted_password", null: false
t.string "salt", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "role"
end
end
Thank you very much in advance!
I think this is what you want to have.
class User.rb
has_many :votes
has_many :hacks, through: :votes
end
class Vote.rb
belongs_to :user
belongs_to :hack
end
class Hack.rb
has_many :votes
end
With this, a hack has many votes through a user.
foreign keys:
votes table: user_id, hack_id
You should be able to do hack.votes
EDIT:
I edited the model to reflect a normal has many through relationship
user -> vote <- hack
a user has many votes
a user has many hacks through votes
a hack has many votes
foreign keys live in the votes table. You can use the following when creating the votes table to indicate the foreign key
t.references user
t.references hack

Which relationship is good to use STI or Polymorphism?

I have read about them but still not clear to me which one I suppose to use and how.
I have User model, Message model and Place model
Message model:
class Message < ActiveRecord::Base
belongs_to :user
end
Messages Table:
create_table "messages", force: true do |t|
t.string "title"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
User model:
class User < ActiveRecord::Base
has_many :messages
end
Users Table:
create_table "users", force: true do |t|
t.string "email"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
end
Now, what I want to do is:
"USER" says "MESSAGES" from "PLACES"
eg. "AHMED" says "HELLO" from "EARTH"
For me both Models (Message and Place) have same data (data type) and same behaviours. So places table should be:
create_table "places", force: true do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
Now may be I'm confused or making big deal than it should be.
What kind of relation should Message and Place have? should it be STI or Polymorphism?
How should I decide?
I'd appreciate the thinking process of how and why I decide specific association.
This example, despite Messages and Places having the same data, doesn't seems a STI/Polymorphism scenario and they should have two different tables.
This could work as a solution:
create_table "users" do |t|
t.string "username"
end
create_table "messages" do |t|
t.string "text"
t.integer "user_id"
t.integer "place_id"
end
create_table "places" do |t|
t.string "name"
end
class User < ActiveRecord::Base
has_many :messages
has_many :places, through: :messages
end
class Place < ActiveRecord::Base
end
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :place
def to_s
"#{user.username} says #{title} from #{place.name}"
end
end
ahmed = User.new(username: "AHMED")
earth = Place.new(name: "EARTH")
message = Message.new(text: "HELLO", user: ahmed, place: earth)
puts message
# => "AHMED says HELLO from EARTH"

Default scope for a joined table in Rails 4

I have to select only program schedule where it belongs to a channel with ctype = TV
but I get this error:
Program (Model)
class Program < ActiveRecord::Base
has_many :program_schedules, dependent: :delete_all
ProgramSchedule (Model)
class ProgramSchedule < ActiveRecord::Base
belongs_to :program
belongs_to :channel
default_scope { includes(:channel).where("program_schedules.channels.ctype NOT IN (?)", ['tv']) }
end
Channel (Model)
class Channel < ActiveRecord::Base
validates :name, :site, :ctype, :country, :presence => true
has_many :programs, :dependent => :delete_all
QUERY
#programs = Program.includes(:program_schedules).where(
"programs.ptype" => ["tv_show","movie"],
"programs.country" => #country).
select("programs.id").
group("programs.id, program_schedules.id, channels.id").
having("program_schedules.stop > ?", Time.current)
Schema.rb
create_table "channels", force: true do |t|
t.string "site"
t.string "name"
t.string "icon"
t.string "ctype"
t.string "country"
t.string "lang"
t.integer "ordertab"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "homepage"
end
create_table "programs", force: true do |t|
t.string "country"
t.string "title"
t.text "subtitle"
t.text "description"
t.float "official_rating", default: 0.0
t.float "rating", default: 0.0
t.string "ptype"
t.string "year"
t.string "imdb"
t.string "tmdb"
t.string "tvdb"
t.string "wiki"
t.string "poster"
t.string "backdrop"
t.string "trailer"
t.float "popularity", default: 0.0
end
create_table "program_schedules", force: true do |t|
t.integer "program_id"
t.datetime "start"
t.datetime "stop"
t.integer "channel_id"
end
Your includes should include the association not the table name in symbolic form. Try changing your default scope to the following:
default_scope { includes(:channel).where("channels.ctype NOT IN (?)", ['tv']) }
I'm posting this as an "answer", but it is intended as a comment. I am doing this so that I can illustrate my explanation better. Let me know if / when you'd like me to remove this non-answer.
I have tried to replicate your problem, and I simply cannot do so using the query and default_scope you've posted so far. Ultimately, the problem is that your LEFT OUTER JOIN condition is referencing a table that has not yet been joined. Even though it is joined later in the query, the interpreter cannot see that far ahead.
This is the part I'm referring to (formatted for readability):
...FROM "programs"
LEFT OUTER JOIN
"program_schedules"
ON "program_schedules"."program_id" = "programs"."id"
AND (channels.ctype NOT IN ...)
^^^
Problem is here. The channels table has not yet been joined.
Additionally, I have never seen a .where appending to the join condition. If you're using that method, ActiveRecord should be placing whatever you passed to the .where method in the WHERE clause, as you'd expect. For some reason, it is obviously appending your condition to the LEFT OUTER JOIN as an AND condition.
The only way I know to append conditions to the join, is by using the .joins method, something like this:
...joins(" AND program_schedules.channels.ctype NOT IN (?)", ['tv'])
That will indeed result in a query like this (truncated for brevity):
LEFT OUTER JOIN "program_schedules" ON ... AND program_schedules.channels......
In addition, the generated query in the error message states
(channels.ctype NOT IN ...)
as the failing join condition, whereas your .where condition states
program_schedules.channels.ctype
I don't see anywhere else in your question where you've written anything that looks like that part of the query in the error message. So again, it seems like we're not quite seeing everything.
It seems like we are still missing some pieces of information. Is it possible you do indeed have something like the .join condition I wrote above? Are there other default_scopes in other models that could be doing this?
I stumbled upon this StackOverflow question when I was researching a similar issue that I'm seeing in my application. I think that using default_scope with joins is just broken.
I boiled your code down to something simple that would fail, ran Program.joins(:program_schedules).to_sql and got the following sql:
SELECT "programs".*
FROM "programs"
INNER JOIN "program_schedules" ON
"program_schedules"."program_id" = "programs"."id" AND
("channels"."ctype" NOT IN ('tv'))
The problem isn't that it hasn't joined to channels yet, it is that it will never join to channels. The where clause from the default_scope got tacked into the INNER JOIN, but the join was discarded.
class Channel < ActiveRecord::Base
has_many :programs
end
class Program < ActiveRecord::Base
has_many :program_schedules
end
class ProgramSchedule < ActiveRecord::Base
belongs_to :program
belongs_to :channel
default_scope { joins(:channel).where.not(channels: { ctype: ['tv'] }) }
end
ActiveRecord::Schema.define(version: 20140331124327) do
create_table "channels", force: true do |t|
t.string "site"
t.string "name"
t.string "icon"
t.string "ctype"
t.string "country"
t.string "lang"
t.integer "ordertab"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "homepage"
end
create_table "programs", force: true do |t|
t.string "country"
t.string "title"
t.text "subtitle"
t.text "description"
t.float "official_rating", default: 0.0
t.float "rating", default: 0.0
t.string "ptype"
t.string "year"
t.string "imdb"
t.string "tmdb"
t.string "tvdb"
t.string "wiki"
t.string "poster"
t.string "backdrop"
t.string "trailer"
t.float "popularity", default: 0.0
end
create_table "program_schedules", force: true do |t|
t.integer "program_id"
t.datetime "start"
t.datetime "stop"
t.integer "channel_id"
end
end
Please take a look at: https://github.com/rails/rails/issues/12896
In activerecord-4.1.7/lib/active_record/scoping/default.rb there's a comment with the next:
# If you need to do more complex things with a default scope, you can
# alternatively define it as a class method:
#
class Article < ActiveRecord::Base
def self.default_scope
# Should return a scope, you can call 'super' here etc.
end
end
Which worked for me.

ActiveScaffold — When inserting a new record, how to enable entry fields for a column/model to which the being created model "belongs_to"

I have the following database schema:
create_table "addresses", :force => true do |t|
t.string "road"
t.string "city"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "client_id"
end
create_table "clients", :force => true do |t|
t.integer "address_id"
t.integer "order_id"
t.string "first_name"
t.string "last_name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "orders", :force => true do |t|
t.integer "order_id"
t.integer "client_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
and models:
class Client < ActiveRecord::Base
belongs_to :address
end
class Order < ActiveRecord::Base
belongs_to :client
end
class Address < ActiveRecord::Base
end
The intention of this setup is to have records of many Clients, each Client has an address. Multiple clients can have the same address. The client_id in the addresses table is used for this purpose.
When I visit the /Clients ActiveScaffold view, and click create I am able to enter data for the new client, including the data of the new address for the client.
But, when I visit the /Orders view and click create, I can add a new Client and enter the data for him, but for the address there is only a select box, which only can be used to select an existing address, there are no fields to create a new address for the new client.
How can I include the address fields for the new client, in order to create a new address for the client?
Thanks in advance
Not really push ActiveScaffold that far, but the associations on your tables look a little strange - you have a foreign key on both sides of the relationship, that is:
addresses has a client_id
and
clients has an address_id
I'd have thought only one side of that is strictly needed, eg client_id on addresses.
Possibly related, but you have belongs_to on either side of the relationship - perhaps one side should be a has_one relationship, that is:
class Client
has_one :address
end
class Address
belongs_to :client
end
Hope that helps, Chris.

Resources