I just have a question regarding how to implement some logic.
Im building a API that allows the client to create orders.
This is solved by a OrderController#create so no problem!
Now, the issue is that an order can have many order-rows, all the relations are set correct but where should i create the order-rows in for the order?
Should the OrderController handle this or should i have a new controller that creates the order-rows for the particular order?
The clients post is sending the following json-data:
{
"status": "paid",
"total_sum": 20,
"payment": "card",
"order_rows": [
{
"id": 12,
},
{
"id":13
}
]
}
I ran into something similar with a project I'm working on now. The best (and long term simplest) solution was definitely to make a whole new model/controller.
*Order
status (should be an int or enum probably)
total (should loop through all order rows and total)
payment (should be an int or enum probably)
has_many order_rows
**OrderRow
belongs_to Order
item_sku
item_name
item_descr
item_cost
etc. etc.
This allows you to easily search for not just items, but orders that include items by name or sku, orders that include items by description.
Your totals are dynamic.
You can retrieve total order numbers or movement numbers on a per item basis.
It is so much easier to create and update orders.
The benefits go on.
It can easily be scoped;
scope :this_orders_rows, -> (order_id) {where(order_id: order_id)}
And it saves you from having to parse through hashes and arrays everytime.
To get technical about it, your order_controller should control ONLY your orders. If you start adding in a heap of other code to read through the arrays its going to get VERY cluttered. It's always better to move that to some other area.
Related
I have a shopping basket which has items in it. My class is BasketItem < ActiveRecord::Base.
A BasketItem belongs_to :item.
Item has many item_tags. It also has many tags through item_tags.
Tags have a key-value set up. The key can be things like "price", "perishable", "produce", etc. The "produce" key has values like "citrus fruit", "berry fruit", "melon fruit", "vegetable", "root", "fungus" and so on.
When pulling a basket, I want the items to come back in a default order of: all the fruits, fungus, then everything else.
In SQL, I'd do my joins and then add:
ORDER BY (
CASE
WHEN tags.value LIKE '%fruit%' THEN 0
WHEN tags.value = 'Vegetable' THEN 1
ELSE 2
END)
I have tried:
has_many :tags, through: :produce
with a default scope of:
default_scope { order(tags: :desc) }
Just to see if I can access the tags, which I can't. In fact, looking at the SQL generate, it's straight up pulling from basket_items with no joins.
1) So how do I order on that tag relationship?
2) How do I get my CASE in there?
3) And how do I make that the default? (If it's not default_scope.)
Thanks!
Going off of Mike Heft's comment and cleaning up the syntax a bit, I ended up with:
default_scope {joins(:tags).order("CASE WHEN tags.name LIKE ...")}
And that created an SQL query that returns things in the right order.
(This ends up being scrambled again, because the code above that is just pulling the IDs for the product, and returning them in that order. I applied the same gag on that level and...still got them scrambled. 🤣So, while I still have to sort out who's responsible for what, it was a simple "join().order()".)
In order to learn Ruby on Rails I am writing a web app that will be used to sort teams within a tournament given their performance to date.
The complication is that I want each tournament organiser (system user) to be able to use a variety of metrics in an arbitrary order.
Expressed as SQL (my background) I want User 1 to be able to choose:
ORDER BY
METRIC1
,METRIC2
,METRIC3
Whilst User 2 could choose:
ORDER BY
METRIC2
,METRIC3
,METRIC1
How would I accept this user input and use it to create a query on the Team table?
Edit 1 Neglected to mention (sorry) that the metrics themselves are calculated on the fly. Currently they are instance methods (e.g #team.metric1 etc). The abortive attempts I have made so far all involve trying to convert user strings to method names which just seems wrong (and I haven't been able to get it to work).
Edit 2 some example code in teams_controller.rb:
class Team < ApplicationRecord
belongs_to :tournament
has_many :matches
def score_for
matches.sum(:score_for)
end
def score_diff
matches.sum(:score_for) - matches.sum(:score_against)
end
end
ActiveRecord allows multiple arguments to be passed to the order method. So you could do something like:
Team.order(:metric2, :metric3, metric1: :desc)
Another options is you can also use ActiveRecord to dynamically construct a query. ActiveRecord queries are lazily evaluated, so the SQL won't be executed until you call an operation that requires loading the records.
For example you could construct a scope on Team like this:
class Team < ApplicationRecord
scope :custom_order, lambda { |sorting_order|
sorting_order.each do |metric|
order(metric)
end
}
end
You would then just need to input a collection of attributes in the order you wanted the order by clauses to be executed. For example:
Team.custom_order([:metric2, :metric3, :metric1])
A working but probably awful solution:
class Tournament < ApplicationRecord
has_many :teams
serialize :tiebreaker, Array
TIEBREAKER_WHITELIST = %w[score opponent_score possession].freeze
def sorted_teams
list = teams.shuffle
(TIEBREAKER_WHITELIST & tiebreaker).reverse.each do |metric|
list = list.sort_by { |team| [team.send(metric), list.find_index(team)] }
end
list.reverse
end
end
Each tournament has many teams. A tournament instance has a serialized field called tiebreaker. This contains an array of strings something like ["score", "possession"] where each string matches the name of a public instance method on team. Each of these methods returns a number.
The tiebreaker field is in descending order of precedence, so for the above example I would only expect possession to affect sorting for teams with an equal score.
list = teams.shuffle - this randomises the list to start with, in case teams are tied for all of the following tiebreakers.
(TIEBREAKER_WHITELIST & tiebreaker) - this returns only strings that appear in both the tiebreaker field and the whitelist constant to protect against end users running arbitrary methods.
.reverse.each do |metric| - this reverses the array of metrics so that the list is sorted by the lowest precedence metric first.
[team.send(metric), list.find_index(team)] - this is the sort for each metric. send turns the string into a method call. I found find_indexwas necessary to preserver sort order from previous sorts. i.e. if I had first sorted for possession this would preserve the order for teams with the same score.
list.reverse - reverse the list then return it. This was because I wanted higher scoring/possession teams first on my list and sort_by sorts ascending.
I wanted some metrics sorted ascending (opponent_score) and others descending (score) so I handled this in the respective methods, returning negative values for opponent_score for example.
I'm not entirely happy with the solution as is but it does seem to work!
I am using this code to create an invoice in Magento:
$invoiceId = Mage::getModel('sales/order_invoice_api')->create($order->getIncrementId(), array());
This automatically assigns a number (increment_id) to the invoice, for example 100016050. I want to create an invoice where the increment_id of the invoice = the increment_id of the order.
How can that be done?
Thanks!
This would require coding a complete custom module, so I'll just explain some basics.
In Magento, entities like order, invoice, creditmemo and shipping each have its own and independant number group per store_id.
These number groups can be defined in the table eav_entity_store:
entity_store_id entity_type_id store_id increment_prefix increment_last_id
1 5 1 1 100000000
2 6 1 2 200000000
3 7 1 3 300000000
4 8 1 4 400000000
To know which entity_type_id refers to which entity, check your eav_entity_type table:
entity_type_id entity_type_code entity_model
5 order sales/order
6 invoice sales/order_invoice
7 creditmemo sales/order_creditmemo
8 shipment sales/order_shipment
Note that your entity_type_id's may (or may not) vary from that.
Magento usually increments each of this entities by one, see eav_entity_type.increment_per_store.
This happens the time such entity is created. But, the creation of an order doesn't always mean, that an invoice for it will be created, too. For example the user could cancel the payment while order placing, or the payment will not be authorized by the payment provider, so no invoice would be created.
This may lead to gaps, e.g. order already at 100000005, while invoice still at 200000002.
Your code would need to manage this gap in a way that keeps order and invoice in sync.
To do this, you could create an observer for the sales_order_invoice_save_before event, for example.
app/code/local/Mycompany/Mymodule/etc/config.xml:
<config>
<modules>
<Mycompany_Mymodule>
<version>0.1.0</version>
</Mycompany_Mymodule>
</modules>
<global>
<models>
<mymodule>
<class>Mycompany_Mymodule_Model</class>
</mymodule>
</models>
<events>
<sales_order_invoice_save_before>
<observers>
<myobserver>
<type>singleton</type>
<class>mymodule/observer</class>
<method>salesOrderInvoiceSaveBefore</method>
</myobserver>
</observers>
</sales_order_invoice_save_before>
</events>
</global>
</config>
app/code/local/Mycompany/Mymodule/Model/Observer.php:
class Mycompany_Mymodule_Model_Observer
{
/**
* Hook to observe `sales_order_invoice_save_before` event
*
* #param Varien_Event_Observer $oObserver
*/
public function salesOrderInvoiceSaveBefore($oObserver)
{
$oInvoice = $oObserver->getInvoice();
}
}
Magento passes the invoice object to this observer, before the invoice object is saved. This would allow you to retrieve the related order object (and therefore the order's increment_id) using this invoice object.
Having retrieved the order.increment_id you could search the invoices to find out whether or not an invoice with that order.increment_id already exists.
If it doesn't exist yet, you can assign the value of order.increment_id to invoice.increment_id before leaving the observer and are done.
Please note, that these are only the basics. There are some more pitfalls to it.
For example, multiple and/or duplicate invoices per order cases are not handled yet.
For example, in some countries the fiscal/tax authorities require invoice numbers to be continuously increasing. It must be 1, 2, 3, 4, 5, but 1, 2, 3, 4 is missing, 5 is not acceptable. Using the technique above, such gaps can still happen, because of payment cancellations by the user, etc.
However, this should get you on the right track.
i would like to have your opinion in a project i am currently working on.
class Product
has_many :orders
end
class Order
attr_accessor :deliverable # to contain temporary data on how many items can be delivered for this order
belongs_to :product
end
somehow i want to have
Order.all_deliverable
that will calculate the Product's quantity, subtract from list of Orders until the Product is empty or there is no more Order for this Product
to illustrate
Product A, quantity: 20
Product B, quantity: 0
Order 1, require Product A, quantity: 12
Order 2, require Product B, quantity: 10
Order 3, require Product A, quantity: 100
so if i call Order.all_deliverable, it will give
Order 1, deliverable:12
Order 3, deliverable: 8 #(20-12)
i have been thinking on using named_scope, but i think the logic will be too complex to be put in a named_scope. Any suggestion?
the pseudo code for all_deliverable will be something like this:
go to each orders
find the remaining quantity for specific product
deduct the product to max amount of order, if product is not enough, add the maximum product
add to the order
end
From what i read around in the web, named_scope deal mostly like find and have not many method calling and looping.
I would use a class method. Named scopes are good for adding to the options list you normally pass to find. You should make them as simple as possible, so that callers can chain them together in a way that makes sense in a particular context, and that allow the scopes to be reused.
Design aside, I'm not sure this can work as a named scope anyway:
Scopes return proxies that delay loading from the database until you access them. I'm not sure how you'd do that when you're computing the records to return.
I'm not sure you can set non-column attributes from within a scope.
Even if the above two items don't apply, the delayed load of scopes means you build it now, but potentially don't load the data until some later time, when it could be stale.
If you just want to manipulate things in a named scope, you can do it like this:
named_scope :foobar, lambda {
# do anything here.
# return hash with options for the named scope
{
:order => whatever,
:limit => 50
}
}
Be aware that Rails 3 deprecates long-used parts of activerecord.
I'm building a report in a Ruby on Rails application and I'm struggling to understand how to use a subquery.
Each 'Survey' has_many 'SurveyResponses' and it is simple enough to retrieve these however I need to group them according to one of the fields, 'jobcode', as I only want to report the information relating to a single jobcode in one line in the report.
However I also need to know the constituent data that makes up the totals for that jobcode. The reason for this is that I need to calculate data such as medians and standard deviations and so need to know the values that make the total.
My thinking is that I retrieve the distinct jobcodes that were reported on for the survey and then as I loop through these I retrieve the individual responses for each jobcode.
Is this the correct way to do this or should I follow a different method?
You could use a named scope to simplify getting the groups of responses:
named_scope :job_group, lambda{|job_code| {:conditions => ["job_code = ?", job_code]}}
Put that in your response model, aand use it like this:
job.responses.job_group('some job code')
and you'll get an array of responses. If you're looking to get the mean of the values of one of the attributes on the responses, you can use map:
r = job.responses.job_group('some job code')
r.map(&:total)
=> [1, 5, 3, 8]
Alternatively, you might find it quicker to write custom SQL in order to get the mean / average / sum of groups of attributes. Going through rails for this sort of work may cause significant lag.
ActiveRecord::Base.connection.execute("Custom SQL here")
You can also use Model.find_by_sql()
For example:
class User < Activerecord::Base
# Your usual AR model
end
...
def index
#users = User.find_by_sql "select * from users"
# etc
end